一文搞懂Mybatis的工作原理

無論是Mybatis也好,Spring也罷,它們的執(zhí)行過程無非可分為啟動階段和運(yùn)行階段:
啟動階段:
- 定義配置文件,如XML、注解
- 解析配置文件,將配置文件加載到內(nèi)存當(dāng)中
運(yùn)行階段:
- 讀取內(nèi)存中的配置文件,并根據(jù)配置文件實(shí)現(xiàn)對應(yīng)的功能
對于執(zhí)行SQL的邏輯來講,有如下步驟:
當(dāng)配置完成之后,假如說我們要執(zhí)行一個(gè)下面一個(gè)sql,那么該如何執(zhí)行呢?
TestMapper testMapper = session.getMapper(TestMapper.class); Test test = testMapper.findOne(1);
一、代理類的生成
首先 MyBatis 會根據(jù)我們傳入接口通過 JDK 動態(tài)代理,生成一個(gè)代理對象 TestMapper,生成邏輯如下所示:
public T newInstance(SqlSession sqlSession) {
// mapperProxy 實(shí)現(xiàn)了 InvocationHandler 接口,用于 JDK 動態(tài)代理
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
// 通過 JDK 動態(tài)代理生成對象
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),
new Class[]{ mapperInterface }, mapperProxy);
}代理類的主要邏輯在 MapperProxy 中,而代理邏輯則是通過 MapperMethod 完成的。
對于 MapperMethod 來說,它在創(chuàng)建的時(shí)候是需要讀取 XML 或者方法注解的配置項(xiàng),所以在使用的時(shí)候才能知道具體代理的方法的 SQL 內(nèi)容。同時(shí),這個(gè)類也會解析和記錄被代理方法的入?yún)⒑统鰠ⅲ苑奖銓?SQL 的查詢占位符進(jìn)行替換,同時(shí)對查詢到的 SQL 結(jié)果進(jìn)行轉(zhuǎn)換。
二、執(zhí)行SQL
代理類生成之后,就可以執(zhí)行代理類的具體邏輯,也就是真正開始執(zhí)行用戶自定義的SQL邏輯了。
首先會進(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;
}通過代碼我們可以很清晰地發(fā)現(xiàn),為什么 MyBatis 的 insert、update 和 delete 會返回行數(shù)的原因。業(yè)務(wù)處理上,我們經(jīng)常通過 update = 1 來判斷當(dāng)前語句是否更新成功。
這里一共做了兩件事情,一件事情是通過 BoundSql 將方法的入?yún)⑥D(zhuǎn)換為 SQL 需要的入?yún)⑿问?,第二件事情就是通過 SqlSession 來執(zhí)行對應(yīng)的 Sql。下面我們通過 select 來舉例。
三、緩存
SqlSession 是 MyBatis 對 SQL 執(zhí)行的封裝,真正的 SQL 處理邏輯要通過 Executor 來執(zhí)行。Executor 有多個(gè)實(shí)現(xiàn)類,因?yàn)樵诓樵冎?,要?check 緩存是否存在,所以默認(rèn)使用的是 CachingExecutor 類,顧名思義,它的作用就是二級緩存。

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

