MyBatis的使用html
configuration全局性配置java
<!-- 獨立使用MyBatis配置 --> <configuration> <!-- 支持多套環境配置 --> <environments default="development"> <environment id="development"> <!-- 兩種事務管理類型:JDBC(手動),MANAGED(Spring或JAVAEE服務器託管) --> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> <environment id="development2"> …… </environment> </environments> <settings cacheModelsEnabled="true" lazyLoadingEnabled="true" enhancementEnabled="true" errorTracingEnabled="true" maxSessions="1024" maxTransactions="512" maxRequests="2048" useStatementNamespaces="true" /> <!-- 維護映射文件 --> <mappers> <package name="org.xiuyuan.mybatis.demo.mapper"/> </mappers> </configuration> <!-- 與Spring結合配置 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <!-- 配置掃描Domain的包路徑 --> <property name="typeAliasesPackage" value="org.xiuyuan.mybatis.demo.domain"/> <!-- 配置掃描Mapper XML的位置 --> <property name="mapperLocations" value="classpath*:org/xiuyuan/mybatis/demo/mapper/*.xml"/> <!-- 配置mybatis配置文件的位置 --> <property name="configLocation" value="classpath:mybatis-config.xml"/> <!-- 註冊類型轉換器 --> <property name="typeHandlers"> <list> <ref bean="dateIntTypeHandler"></ref> </list> </property> </bean>
MyBatis 真正的着力點是在映射語句中,與JDBC相比將節省95%的代碼量 ,對於使用StatemetMapper接口的DAO層實現,比hibernate代碼還要少.git
cache - 配置給定命名空間的緩存。
cache-ref – 從其餘命名空間引用緩存配置。
resultMap – 最複雜標籤,用來描述如何從數據庫結果集中來加載對象。
sql – 能夠重用的 SQL 塊,也能夠被其餘語句引用。
insert – 映射插入語句
update – 映射更新語句
delete – 映射刪除語句
select – 映射查詢語句github
//第一種使用xml和徹底限定名調用映射 //這裏注意當心namespace ,parameterType ,resultType這幾項配置 //sql列別名對應pojo屬性名,自動映射 <mapper namespace="org.xiuyuan.mybatis.demo.mapper.BranchStatusMapper"> <select id="getBranchStatusListById" parameterType="int" resultType="Blog"> select branch_id "branchId",is_signed "isSigned",is_online "isOnline",type, cooperate_times "cooperateTimes",created_time "createdTime",last_modified "lastModified" from branch_status where branch_id = #{id} </select> </mapper> BranchStatusMapper branchStatus = (branchStatus) session.selectOne(this.getClass.getName()+".getBranchStatusListById", 101); //第二種採用接口調用註解映射語句 public interface BranchStatusMapper { @SelectProvider(type = BrnchStatusSqlProvider.class, method = "getBrnchStatusById") //或者直接寫sql @Select("select branch_id "branchId",is_signed "isSigned",cooperate_times "cooperateTimes" from branch_status where branch_id = #{id}") @Options(useCache = true, flushCache = false, timeout = 10000) List<BranchStatus> getBranchStatusListById(Integer id); } //第三種採用SQL提供類
public class BranchStatusSqlProvider { public String getBrnchStatusById(Map<String, Object> parameters) { BEGIN(); SELECT("branch_id "branchId",is_signed "isSigned",is_online "isOnline",type,cooperate_times "cooperateTimes",created_time "createdTime",last_modified "lastModified""); FROM("branch_status"); WHERE("branch_id = #{id}"); return SQL(); } }
BranchStatusMapper mapper = session.getMapper(BranchStatusMapper.class); BranchStatus branchStatus = mapper.getBranchStatusListById(101); //第三種採用接口調用xml映射語句(推薦這種方式,須要spring-mybatis) <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.sankuai.meituan.crm.dao.mapper.BranchStatusMapper"> <sql id="branch_status_column_property"> <![CDATA[ branch_id "branchId",is_signed "isSigned",is_online "isOnline",type,cooperate_times "cooperateTimes",created_time "createdTime",last_modified "lastModified" ]]> </sql> </mapper> // 接口定義 public interface BranchStatusMapper { List<BranchStatus> getBranchStatusListById(Integer id); } <!-- 配置掃描Mapper接口的包路徑 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.sankuai.meituan.crm.dao.mapper"/> </bean> //可直接在service注入,mybatis動態生成接口實現類 @Resource private BranchStatusMapper branchStatusMapper;
XML映射細節算法
我推薦使用xml的方式,註解方式不便於SQL語句的修改和優化spring
//SQL片斷 (抽取出公共部分供多出調用) <sql id="partner_table_columns"> <![CDATA[ name, city_id, district_id, qualification_id, qualification_code, qualification_type, creator_id, create_time, modifier_id, modify_time, status, integrity, expire_date, lock_version ]]> </sql> <sql id="partner_java_property"> <![CDATA[ #{name}, #{cityId}, #{districtId}, #{qualificationId}, #{qualificationCode}, #{qualificationType}, #{creatorId}, #{createTime}, #{modifierId}, #{modifyTime}, #{status}, #{integrity}, #{expireDate}, #{lockVersion} ]]> </sql> //主鍵策略: //若是使用的數據庫支持自動生成主鍵,那麼就能夠設置 useGeneratedKeys= 」true」 ,把keyProperty 設成對應的列。 <insert id="insert" useGeneratedKeys="true" keyProperty="id" parameterType="Partner"> insert into partner(<include refid="partner_table_columns"/> ) values (<include refid="partner_java_property"/>) </insert>
<update id="update" parameterType="BranchStatus"> update branch_status <set> <if test="isSigned != null">is_signed=#{isSigned},</if> <if test="isOnline != null">is_online=#{isOnline},</if> <if test="type != null">type=#{type},</if> <if test="cooperateTimes != null">cooperate_times=#{cooperateTimes},</if> last_modified = unix_timestamp() </set> where branch_id = #{branchId} </update> <delete id="deleteBranchStatus」 parameterType="int"> delete from branch_user where branch_id = #{id} </delete>
<!-- 動態映射 --> <!-- if, where ,foreach標籤,簡化動態sql和格式控制成本 --> <select id="getBranchIdsByParams" parameterType="map" resultType="int"> select bs.branch_id from branch_status bs <if test="(statusList != null and statusList.size > 0) or expireTime !=null"> inner join branch_user bu on bu.branch_id = bs.branch_id </if> <where> <if test="isSigned != null"> bs.is_signed = #{isSigned} </if> <if test="isOnline != null"> and bs.is_online = #{isOnline} </if> <if test="statusList != null and statusList.size > 0"> and bu.status in <foreach collection="statusList" index="index" item="status" open="(" separator="," close=")"> #{status} </foreach> </if> <if test="expireTime != null"> <![CDATA[ and bu.expire_time > 0 and bu.expire_time < #{expireTime} ]]> </if> </where> </select>
ResoutMap映射,處理一對一,一對多,多對多等複雜狀況,sql
不建議大範圍使用,建議把鏈接拆分大聯接數據庫
<!-- 注意resultMap的命名 --> <select id="selectBlogDetails" parameterType="int" resultMap="detailedBlogResultMap"> select B.id as blog_id, B.title as blog_title, B.author_id as blog_author_id, A.id as author_id, A.username as author_username, A.password as author_password, A.email as author_email, A.bio as author_bio, A.favourite_section as author_favourite_section, P.id as post_id, P.blog_id as post_blog_id, P.author_id as post_author_id, P.created_on as post_created_on, P.section as post_section, P.subject as post_subject, P.draft as draft, P.body as post_body, C.id as comment_id, C.post_id as comment_post_id, C.name as comment_name, C.comment as comment_text, T.id as tag_id, T.name as tag_name from Blog B left outer join Author A on B.author_id = A.id left outer join Post P on B.id = P.blog_id left outer join Comment C on P.id = C.post_id left outer join Post_Tag PT on PT.post_id = P.id left outer join Tag T on PT.tag_id = T.id where B.id = #{id} </select> <!-- resultMap映射 --> <resultMap id="detailedBlogResultMap" type="Blog"> <constructor> <idArg column="blog_id" javaType="int"/> </constructor> <result property="title" column="blog_title"/> <association property="author" javaType="Author"> <id property="id" column="author_id"/> <result property="username" column="author_username"/> <result property="password" column="author_password"/> <result property="email" column="author_email"/> <result property="bio" column="author_bio"/> <result property="favouriteSection" column="author_favourite_section"/> </association> <collection property="posts" ofType="Post"> <id property="id" column="post_id"/> <result property="subject" column="post_subject"/> <association property="author" javaType="Author"/> <collection property="comments" ofType=" Comment"> <id property="id" column="comment_id"/> </collection> <collection property="tags" ofType=" Tag" > <id property="id" column="tag_id"/> </collection> <discriminator javaType="int" column="draft"> <case value="1" resultType="DraftPost"/> </discriminator> </collection> </resultMap>
準備工做緩存
Mybatis完成SQL查詢須要的最簡代碼以下:安全
String resource = "mybatis.cfg.xml"; Reader reader = Resources.getResourceAsReader(resource); SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(reader); SqlSession session = ssf.openSession(); try { BranchUser user = (BranchUser) session.selectOne("BranchUserDao.getBranchUserById", "1"); System.out.println(user); } finally { session.close(); }
SqlSession session = ssf.openSession(); //DefaultSqlSessionFactory的 openSession()方法內容以下: public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); } //openSessionFromDataSource內部 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { //獲取加載配置文件的環境信息 final Environment environment = configuration.getEnvironment(); //設置鏈接的事務信息(是否自動提交、事務級別),從配置環境中獲取事務工廠,事務工廠獲取一個新的事務。 final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); //傳入事務對象獲取一個新的執行器,並傳入執行器、配置信息等獲取一個執行會話對象。 final Executor executor = configuration.newExecutor(tx, execType, autoCommit); return new DefaultSqlSession(configuration, executor); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } // 在openSessionFromDataSource代碼中重點在newExecutor和DefaultSqlSession //newExecutor到底作了什麼? public Executor newExecutor (Transaction transaction, ExecutorType executorType){ //判斷執行器類型,若是配置文件中沒有配置執行器類型,則採用默認執行類型ExecutorType.SIMPLE。 executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; // 根據執行器類型返回不一樣類型的執行器(執行器有三種,分別是 BatchExecutor、SimpleExecutor和CachingExecutor) Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } if (cacheEnabled) { executor = new CachingExecutor(executor); } //執行器綁定攔截器插件 executor = (Executor) interceptorChain.pluginAll(executor); return executor; } //DefaultSqlSession 是作什麼的? //DefaultSqlSession實現了SqlSession接口,裏面有各類各樣的SQL執行方法,主要用於SQL操做的對外接口,它會的調用執行器來執行實際的SQL語句。 session.selectOne("BranchUserDao.getBranchUserById", "1"); //selectOne方法實現 public Object selectOne (String statement, Object parameter){ // Popular vote was to return null on 0 results and throw exception on too many. List list = selectList(statement, parameter); if (list.size() == 1) { return list.get(0); } else if (list.size() > 1) { throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); } else { return null; } } //本質上都是調用selectList實現 public List selectList (String statement, Object parameter){ return selectList(statement, parameter, RowBounds.DEFAULT); } public List selectList (String statement, Object parameter, RowBounds rowBounds){ try { //根據SQL的ID到配置信息中找對應的MappedStatement,初始化時MyBatis會將SQL塊解析並放入Map<String, MappedStatement> mappedStatements 中 //並將MappedStatement對象放到一個Map裏面進行存放,Map的key值是該SQL塊的ID。 MappedStatement ms = configuration.getMappedStatement(statement); //調用執行器的query方法,傳入MappedStatement對象、SQL參數對象、範圍對象和結果處理方式。 return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } //執行器(SimpleExecutor)執行sql代碼分析 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { //獲取配置信息對象。 Configuration configuration = ms.getConfiguration(); //經過配置對象獲取一個新的StatementHandler,生成結果處理對象(見下文)。 StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql); //預處理StatementHandler對象,獲得Statement對象。 stmt = prepareStatement(handler, ms.getStatementLog()); //傳入Statement和結果處理對象,經過StatementHandler的query方法來執行SQL,並對執行結果進行處理。 return handler.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); } } //newStatementHandler方法分析 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { //根據相關的參數獲取對應的StatementHandler對象。 StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); //爲StatementHandler對象綁定攔截器插件。 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; } //RoutingStatementHandler構造方法分析 //根據 MappedStatement對象的StatementType來建立不一樣的StatementHandler,這個跟前面執行器代碼相似 //StatementType有STATEMENT、PREPARED和CALLABLE三種類型,跟JDBC的Statement類型對應 public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { switch (ms.getStatementType()) { case STATEMENT: delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case PREPARED: delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case CALLABLE: delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; default: throw new ExecutorException("Unknown statement type: " + ms.getStatementType()); } } //最後執行prepareStatement方法,執行SQL獲取結果. private Statement prepareStatement (StatementHandler handler)throws SQLException { Statement stmt; Connection connection = transaction.getConnection(); //從鏈接中獲取Statement對象 stmt = handler.prepare(connection); //處理預編譯的傳入參數 handler.parameterize(stmt); return stmt; }
一級緩存基於 PerpetualCache 的 HashMap 本地緩存,其存儲做用域爲 Session,當 Session flush 或 close 以後,該Session中的全部 Cache 就將清空。
二級緩存與一級緩存其機制相同,默認也是採用 PerpetualCache,HashMap存儲,可是它的存儲做用域爲 Mapper(Namespace),而且可自定義存儲源,如 Ehcache、Memcached等。
對於緩存數據更新機制,當某一個做用域(一級緩存Session/二級緩存Namespaces)的進行了 insert/update/delete 操做後,默認該做用域下全部 select 中的緩存將被clear。
MyBatis 的緩存採用了delegate機制及裝飾器模式設計,當put、get、remove時,其中會通過多層 delegate cache 處理,其Cache類別有:BaseCache(基礎緩存)、EvictionCache(排除算法緩存) 、DecoratorCache(裝飾器緩存):
MyBatis 對於其 緩存Key 的生成採起規則爲:[hashcode : checksum : mappedStementId : offset : limit : executeSql : queryParams] 見代碼:BaseExecutor.createCacheKey
myBatis對Cache的處理流程:
public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); // 執行器已關閉 if (closed) throw new ExecutorException("Executor was closed."); List list; try { queryStack++; // 建立緩存Key CacheKey key = createCacheKey(ms, parameter, rowBounds); // 從本地緩存在中獲取該 key 所對應的結果集 final List cachedList = (List) localCache.getObject(key); // 在緩存中找到數據 if (cachedList != null) { list = cachedList; } else { // 未從本地緩存中找到數據,開始調用數據庫查詢 //爲該 key 添加一個佔位標記 localCache.putObject(key, EXECUTION_PLACEHOLDER); try { // 執行子類所實現的數據庫查詢 操做 list = doQuery(ms, parameter, rowBounds, resultHandler); } finally { // 刪除該 key 的佔位標記 localCache.removeObject(key); } // 將db中的數據添加至本地緩存中 localCache.putObject(key, list); } } finally { queryStack--; } // 刷新當前隊列中的全部 DeferredLoad實例,更新 MateObject if (queryStack == 0) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } } return list; }
public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { if (ms != null) { // 獲取二級緩存實例 Cache cache = ms.getCache(); if (cache != null) { flushCacheIfRequired(ms); // 獲取 讀鎖( Read鎖可由多個Read線程同時保持) cache.getReadWriteLock().readLock().lock(); try { // 當前 Statement 是否啓用了二級緩存 if (ms.isUseCache()) { // 將建立 cache key 委託給 BaseExecutor 建立 CacheKey key = createCacheKey(ms, parameterObject, rowBounds); final List cachedList = (List) cache.getObject(key); // 從二級緩存中找到緩存數據 if (cachedList != null) { return cachedList; } else { // 未找到緩存,很委託給 BaseExecutor 執行查詢 List list = delegate.query(ms, parameterObject, rowBounds, resultHandler); tcm.putObject(cache, key, list); return list; } } else { // 沒有啓動用二級緩存,直接委託給 BaseExecutor 執行查詢 return delegate.query(ms, parameterObject, rowBounds, resultHandler); } } finally { // 當前線程釋放 Read 鎖 cache.getReadWriteLock().readLock().unlock(); } } } return delegate.query(ms, parameterObject, rowBounds, resultHandler); }
Configuration configuration = sqlSession.getConfiguration(); MappedStatement ms = configuration.getMappedStatement(sqlStatementId); BoundSql boundSql = ms.getBoundSql(param); String sql = boundSql.getSql();
//1:實現 TypeHandler轉換接口 /** * @author zhangyijun * java中的Date和jdbc中的int轉換 */ //註解生命須要轉換的類型 @MappedTypes(value = Date.class) @MappedJdbcTypes(value = JdbcType.NUMERIC) public class DateIntTypeHandler implements TypeHandler<Date> { @Override public Date getResult(CallableStatement cs, int columnIndex) throws SQLException { String value = cs.getString(columnIndex); if (StringUtil.isBlank(value)) { Integer time = Integer.valueOf(value); return DateUtil.fromUnixTime(time); } return null; } @Override public void setParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType) throws SQLException { if (parameter != null) { Integer second = DateUtil.date2Unixtime(parameter); ps.setInt(i, second); } } @Override public Date getResult(ResultSet rs, String columnName) throws SQLException { String value = rs.getString(columnName); if (StringUtil.isBlank(value)) { Integer time = Integer.valueOf(value); return DateUtil.fromUnixTime(time); } return null; } @Override public Date getResult(ResultSet rs, int columnIndex) throws SQLException { String value = rs.getString(columnIndex); if (StringUtil.isBlank(value)) { Integer time = Integer.valueOf(value); return DateUtil.fromUnixTime(time); } return null; } } //2:註冊類型轉換器 <!-- 類型轉換器 --> <bean id="dateIntTypeHandler" class="org.xiuyuan.mybatis.demo.dao.handler.DateIntTypeHandler"></bean> <!-- 註冊類型轉換器 --> <property name="typeHandlers"> <list> <ref bean="dateIntTypeHandler"></ref> </list> </property> //3:聲明須要類型轉換的字段 #{createdTime,javaType=Date,jdbcType=NUMERIC}
-- 查詢branch_status全部列 select group_concat(column_name) from information_schema.columns where table_schema = 'you_database' and table_name = 'you_table' -- 生成列對應的java屬性 select group_concat(case when locate('_',column_name)>0 then concat(replace(column_name , concat('_',@shortChar:=substring(column_name,locate('_',column_name)+1,1)),upper(@shortChar))) else column_name end) from information_schema.columns where table_schema = 'you_database' and table_name = 'branch_status'
district_id "districtId" -- 生成列+別名(屬性名 如:is_online "isOnline") select group_concat(case when locate('_',column_name)>0 then concat(column_name,' "',replace(column_name , concat('_',@shortChar:=substring(column_name,locate('_',column_name)+1,1)),upper(@shortChar)),'"') else column_name end) from information_schema.columns where table_schema = 'you_database' and table_name = 'branch_status'
-- 生成直接提取ognl表達式(如:#{branchId}) select group_concat(concat('#{',replace(column_name , concat('_',@shortChar:=substring(column_name,locate('_',column_name)+1,1)), upper(@shortChar) ),'}')) from information_schema.columns where table_schema = 'you_database' and table_name = 'branch_status'
-- 生成加入用於判空表達式,用於update(如:<if test = "name != null">name=#{name},</if>) select group_concat(concat('<if test = "' , @java_column_name:=replace(column_name , concat('_',@shortChar:=substring(column_name,locate('_',column_name)+1,1)), upper(@shortChar) ) , ' != null">', column_name , '=#{',@java_column_name,'},</if>') SEPARATOR '') from information_schema.columns where table_schema = 'you_database' and table_name = 'branch_status'
<if test = "name != null"> and name=#{name}</if> -- 生成加入用於判空表達式,用於where條件(如:<if test = "isSigned != null"> and is_signed=#{isSigned},) select group_concat(concat('<if test = "' , @java_column_name:=replace(column_name , concat('_',@shortChar:=substring(column_name,locate('_',column_name)+1,1)), upper(@shortChar) ) , ' != null"> and ', column_name , '=#{', @java_column_name,'},</if>') SEPARATOR '') from information_schema.columns where table_schema = 'you_database' and table_name = 'branch_status'
-- 配置全局屬性 autoMappingBehavior=FULL -- 當心性能開銷 TODO 測試 select u.branch_id "branchId" , s.type "branchStatus.type" ... from branch_user u, branc_status s where s.branch_id = u.branch_id
-- mapUnderscoreToCamelCase =true //默認false -- 自動映射,無需再經過列別名指定屬性名 select branch_id,is_signed,is_online,type,cooperate_times,created_time,last_modified from branch_status where branch_id = #{id}
使用#{}格式 MyBatis 建立預處理語句屬設置安全的值(?) 。
使用${}格式 直接在 SQL 語 句中插入一個不改變的字符串,有sql注入風險。