一文搞懂Mybatis的工作原理

無(wú)論是Mybatis也好,Spring也罷,它們的執(zhí)行過(guò)程無(wú)非可分為啟動(dòng)階段和運(yùn)行階段:
啟動(dòng)階段:
- 定義配置文件,如XML、注解
- 解析配置文件,將配置文件加載到內(nèi)存當(dāng)中
運(yùn)行階段:
- 讀取內(nèi)存中的配置文件,并根據(jù)配置文件實(shí)現(xiàn)對(duì)應(yīng)的功能
對(duì)于執(zhí)行SQL的邏輯來(lái)講,有如下步驟:
當(dāng)配置完成之后,假如說(shuō)我們要執(zhí)行一個(gè)下面一個(gè)sql,那么該如何執(zhí)行呢?
TestMapper testMapper = session.getMapper(TestMapper.class); Test test = testMapper.findOne(1);
一、代理類的生成
首先 MyBatis 會(huì)根據(jù)我們傳入接口通過(guò) JDK 動(dòng)態(tài)代理,生成一個(gè)代理對(duì)象 TestMapper,生成邏輯如下所示:
public T newInstance(SqlSession sqlSession) {
// mapperProxy 實(shí)現(xiàn)了 InvocationHandler 接口,用于 JDK 動(dòng)態(tài)代理
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
// 通過(guò) JDK 動(dòng)態(tài)代理生成對(duì)象
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),
new Class[]{ mapperInterface }, mapperProxy);
}代理類的主要邏輯在 MapperProxy 中,而代理邏輯則是通過(guò) MapperMethod 完成的。
對(duì)于 MapperMethod 來(lái)說(shuō),它在創(chuàng)建的時(shí)候是需要讀取 XML 或者方法注解的配置項(xiàng),所以在使用的時(shí)候才能知道具體代理的方法的 SQL 內(nèi)容。同時(shí),這個(gè)類也會(huì)解析和記錄被代理方法的入?yún)⒑统鰠?,以方便?duì) SQL 的查詢占位符進(jìn)行替換,同時(shí)對(duì)查詢到的 SQL 結(jié)果進(jìn)行轉(zhuǎn)換。
二、執(zhí)行SQL
代理類生成之后,就可以執(zhí)行代理類的具體邏輯,也就是真正開始執(zhí)行用戶自定義的SQL邏輯了。
首先會(huì)進(jìn)入到 MapperMethod 核心的執(zhí)行邏輯,如下所示:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
// ...
return result;
}通過(guò)代碼我們可以很清晰地發(fā)現(xiàn),為什么 MyBatis 的 insert、update 和 delete 會(huì)返回行數(shù)的原因。業(yè)務(wù)處理上,我們經(jīng)常通過(guò) update = 1 來(lái)判斷當(dāng)前語(yǔ)句是否更新成功。
這里一共做了兩件事情,一件事情是通過(guò) BoundSql 將方法的入?yún)⑥D(zhuǎn)換為 SQL 需要的入?yún)⑿问剑诙虑榫褪峭ㄟ^(guò) SqlSession 來(lái)執(zhí)行對(duì)應(yīng)的 Sql。下面我們通過(guò) select 來(lái)舉例。
三、緩存
SqlSession 是 MyBatis 對(duì) SQL 執(zhí)行的封裝,真正的 SQL 處理邏輯要通過(guò) Executor 來(lái)執(zhí)行。Executor 有多個(gè)實(shí)現(xiàn)類,因?yàn)樵诓樵冎?,要?check 緩存是否存在,所以默認(rèn)使用的是 CachingExecutor 類,顧名思義,它的作用就是二級(jí)緩存。

CachingExecutor 的執(zhí)行邏輯如下所示:
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds,
ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 放緩存
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 若二級(jí)緩存為空,則重新查詢數(shù)據(jù)庫(kù)
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}二級(jí)緩存是和命名空間綁定的,如果多表操作的 SQL 的話,是會(huì)出現(xiàn)臟數(shù)據(jù)的。同時(shí)如果是不同的事務(wù),也可能引起臟讀,所以要慎重。
如果二級(jí)緩存沒(méi)有命中,則會(huì)進(jìn)入到 BaseExecutor 中繼續(xù)執(zhí)行,在這個(gè)過(guò)程中,會(huì)調(diào)用一級(jí)緩存執(zhí)行。
值得一提的是,在 MyBatis 中,緩存分為 PerpetualCache、BlockingCache、LruCache 等,這些 cache 的實(shí)現(xiàn)則是借用了裝飾者模式。一級(jí)緩存使用的是 PerpetualCache,里面是一個(gè)簡(jiǎn)單的 HashMap。一級(jí)緩存會(huì)在更新的時(shí)候,事務(wù)提交或者回滾的時(shí)候被清空。換句話說(shuō),一級(jí)緩存是和 SqlSession 綁定的。
四、查詢數(shù)據(jù)庫(kù)
如果一級(jí)緩存中沒(méi)有的話,則需要調(diào)用JDBC執(zhí)行真正的SQL邏輯。我們知道,在調(diào)用JDBC之前,是需要建立連接的,如下代碼所示:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}我們會(huì)發(fā)現(xiàn),Mybatis并不是直接從JDBC獲取連接的,通過(guò)數(shù)據(jù)源來(lái)獲取的,Mybatis默認(rèn)提供了是那種數(shù)據(jù)源:JNDI,PooledDataSource和UnpooledDataSource,我們也可以引入第三方數(shù)據(jù)源,如Druid等。包括驅(qū)動(dòng)等都是通過(guò)數(shù)據(jù)源獲取的。
獲取到Connection之后,還不夠,因?yàn)镴DBC的數(shù)據(jù)庫(kù)操作是需要Statement的,所以Mybatis專門抽象出來(lái)了 StatementHandler 處理類來(lái)專門處理和JDBC的交互,如下所示:
SimpleStatementHandler
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.<E>handleResultSets(statement);
}其實(shí)這三代代碼就代表了Mybatis執(zhí)行SQL的核心邏輯:組裝SQL,執(zhí)行SQL,組裝結(jié)果。僅此而已。
具體Sql是如何組裝的呢?是通過(guò)BoundSql來(lái)完成的,具體組裝的邏輯大家可以從 org.apache.ibatis.mapping.MappedStatement#getBoundSql 中了解,這里不再贅述。
五、處理查詢結(jié)果
當(dāng)我們獲取到查詢結(jié)果之后,就需要對(duì)查詢結(jié)果進(jìn)行封裝,即把查詢到的數(shù)據(jù)庫(kù)字段映射為DO對(duì)象。
因?yàn)榇藭r(shí)我們已經(jīng)拿到了執(zhí)行結(jié)果ResultSet,同時(shí)我們也在應(yīng)用啟動(dòng)的時(shí)候在配置文件中配置了DO到數(shù)據(jù)庫(kù)字段的映射ResultMap,所以通過(guò)這兩個(gè)配置就可以轉(zhuǎn)換。核心的轉(zhuǎn)換邏輯是通過(guò)TypeHandler完成的,流程如下所示:
- 創(chuàng)建返回的實(shí)體類對(duì)象,如果該類是延遲加載,則先生成代理類
- 根據(jù)ResultMap中配置的數(shù)據(jù)庫(kù)字段,將該字段從ResultSet取出來(lái)
- 從ResultMap中獲取映射關(guān)系,如果沒(méi)有,則默認(rèn)將下劃線轉(zhuǎn)為駝峰式命名來(lái)映射
- 通過(guò)setter方法反射調(diào)用,將數(shù)據(jù)庫(kù)的值設(shè)置到實(shí)體類對(duì)象當(dāng)中
到此這篇關(guān)于一文搞懂Mybatis的工作原理的文章就介紹到這了,更多相關(guān)mybatis工作原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java集合框架Collections原理及用法實(shí)例
這篇文章主要介紹了Java集合框架Collections原理及用法實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08
在SpringBoot項(xiàng)目中連接SQL Server的幾種實(shí)現(xiàn)方式
本文介紹了在SpringBoot項(xiàng)目中連接SQLServer的三種常見方法:使用Microsoft官方的JDBC驅(qū)動(dòng)、第三方的jTDS驅(qū)動(dòng)以及Microsoft提供的官方SpringBootStarter,每種方法均提供了對(duì)應(yīng)的依賴配置和YAML配置示例,幫助開發(fā)者輕松實(shí)現(xiàn)數(shù)據(jù)庫(kù)連接2025-09-09
SpringBoot啟動(dòng)器Starters使用及原理解析
這篇文章主要介紹了SpringBoot啟動(dòng)器Starters使用及原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04
MyBatis-Generator的配置說(shuō)明和使用
本文主要介紹了MyBatis-Generator的配置說(shuō)明和使用的相關(guān)知識(shí)。具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧2017-02-02
使用synchronized實(shí)現(xiàn)一個(gè)Lock代碼詳解
這篇文章主要介紹了使用synchronized實(shí)現(xiàn)一個(gè)Lock代碼詳解,具有一定借鑒價(jià)值,需要的朋友可以參考下。2017-12-12
java智能問(wèn)答圖靈機(jī)器人AI接口(聚合數(shù)據(jù))

