本文主旨
方便你快速分析mybatis源碼,梳理其流程,掌握主要知識點.
官方簡介:
MyBatis 是一款優(yōu)秀的持久層框架,它支持定制化 SQL、存儲過程以及高級映射。MyBatis 避免了幾乎所有的 JDBC 代碼和手動設(shè)置參數(shù)以及獲取結(jié)果集。MyBatis 可以使用簡單的 XML 或注解來配置和映射原生類型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 對象)為數(shù)據(jù)庫中的記錄。
本文介紹
mybatis相信大家都用的多了,本文主要介紹mybatis的整體設(shè)計思想、核心源碼實現(xiàn),并從中體悟面向?qū)ο笏季S的設(shè)計理念.-----哈哈,正所謂,技近乎藝,藝近乎道!
架構(gòu)介紹

其結(jié)構(gòu)可以分為三層
- 配置初始化:負(fù)責(zé)mybatis的初始化,環(huán)境、數(shù)據(jù)、插件配置組裝工作
- 調(diào)用會話: 一次查詢的調(diào)用過程
- 映射轉(zhuǎn)換: java對象、數(shù)據(jù)庫結(jié)果間相互映射處理
源碼流程梳理
層級如此,我們再來看看調(diào)用時序:

- Configuration作為mybatis的頂層配置類,管理一切mybatis的環(huán)境、緩存信息
2.當(dāng)你調(diào)用getMapper方法獲取對應(yīng)mapper類時(或通過spring注入mapper類時)其實最后調(diào)用了Configuration里的getMapper方法, 看下getMapper對應(yīng)代碼:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
其實調(diào)用的是對應(yīng)的緩存類MapperRegistry管理Mapper代理對象
- 打開MapperRegistry類,熟悉的代理模式方法
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
此處獲取對應(yīng)的MapperProxy代理類,看下此類核心invoker方法:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//聲明對應(yīng)代理內(nèi)部方法直接調(diào)用,如object的方法
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
//最后實際執(zhí)行方法,還是回到了SqlSession接口類上
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
看完MapperProxy代理類,我們知道m(xù)ybatis最終返回給用戶使用的是MapperProxy類,但實際干活的還是SqlSession對象,但SqlSession實際是會話級對象,即其scope作用域為線程級別,那此處這個代理類中的成員變量是怎么回事呢?
答案是此處的SqlSession實現(xiàn)類為SqlSessionTemplate類,其是SqlSession默認(rèn)實現(xiàn)的模版方法類,屬于application級別,看下其構(gòu)造方法
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
//此處生成對應(yīng)的sqlSession代理執(zhí)行類,每個線程將在SqlSessionInterceptor創(chuàng)建單獨的SqlSession
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
- 原來又是一層動態(tài)代理啊!繼續(xù)看其invoker方法,實際分為下面三部
- 獲取實際會話對象,此時注意同一個事務(wù)中可以獲取相同的SqlSession
- 調(diào)用實際會話對象相應(yīng)方法
- 關(guān)閉資源
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//獲取線程級別SqlSession會話對象,同一個事務(wù)中可以獲取相同的SqlSession,進(jìn)行統(tǒng)一事務(wù)和緩存、延遲加載等處理
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
//調(diào)用實際sqlSession會話對象
Object result = method.invoke(sqlSession, args);
............
//關(guān)閉資源
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
- 經(jīng)過多層代理,終于找到了我們會話級別的SqlSession,當(dāng)然還有一些細(xì)節(jié)流程,如事務(wù)處理、 TransactionSynchronizationManager(便于用戶定義事務(wù)提交后處理流程) 處理器管理等等
8.下面SqlSession的實現(xiàn)類,默認(rèn)為DefaultSqlSession,在mybatis中認(rèn)為,任何查詢返回結(jié)果都概括為一個list,單個結(jié)果只是特殊的list,所以selectList方法為其核心查詢方法
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
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();
}
}
9.首先MappedStatement為定義的方法對應(yīng)的sql信息,映射信息等等數(shù)據(jù),executor又是一種重要概念,代表了mybatis中最終的執(zhí)行器.
- 繼續(xù),BaseExecutor明細(xì)的模版方法,執(zhí)行query,跟代碼注釋梳理下核心邏輯:
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
//ErrorContext存儲了對應(yīng)的myatis調(diào)用棧信息,便于排查錯誤
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
//查詢堆棧,
queryStack++;
// 查看一級緩存是否存在緩存結(jié)果
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//沒有數(shù)據(jù)從數(shù)據(jù)庫獲取數(shù)據(jù)
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
//對于延時加載的處理
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
c.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
此處,我們看到了熟悉的mybatis一級緩存概念,對于關(guān)聯(lián)查詢等操作,一級緩存還是很有用的,懶加載此處也有實現(xiàn),在結(jié)果集映射時也會操作deferredLoads此對象,進(jìn)行懶加載操作.
順便此處說下二級緩存.mybatis的二級緩存通過CachingExecutor實現(xiàn),其是對BaseExecutor的包裝類,當(dāng)緩存中沒有時,一樣會調(diào)用BaseExecutor相應(yīng)方法進(jìn)行從數(shù)據(jù)庫中獲取數(shù)據(jù).
- 下面核心落到了queryFromDatabase方法中,
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
//通過占位符方式,標(biāo)注是否有緩存數(shù)據(jù)存入
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
//調(diào)用實際查詢方法
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
//一級緩存數(shù)據(jù)
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
- 下面進(jìn)入SimpleExecutor類的doQuery方法
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
//獲取數(shù)據(jù)庫鏈接
Configuration configuration = ms.getConfiguration();
//包裝 Statement對象,此處一樣用委派模式,進(jìn)行包裝不同的處理類,默認(rèn)實現(xiàn)為PreparedStatementHandler,并且對于mybatis的plugins也是在此處進(jìn)行封裝
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//處理參數(shù)驗證,TypeHandler等轉(zhuǎn)換在此前置處理
stmt = prepareStatement(handler, ms.getStatementLog());
//最后通過此PreparedStatementHandler執(zhí)行query方法,并通過resultHandler進(jìn)行結(jié)果映射
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
- 下面就簡單了
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//熟悉的jdbc操作,獲取db數(shù)據(jù)
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
//結(jié)果集映射處理
return resultSetHandler.<E> handleResultSets(ps);
}
- 在resultSetHandler中,會通過反射創(chuàng)建對應(yīng)的對象實體,賦值到結(jié)果集中,如果有懶加載需要,會添加deferredLoad對列,等待用的時候加載對應(yīng)屬性.
總結(jié)
好了,mybatis主要遠(yuǎn)嘛流程梳理完了.
mybatis多處用到了動態(tài)代理模式,方便使用者,sql和接口定義分離模式
其中對于SqlSession的代理Template封裝很巧妙,充分學(xué)習(xí)了模版類的神奇之處
ErrorContext實現(xiàn)對于異常排查問題,方便使用
代理無處不在,handler方便擴(kuò)展,mybatis主旨在于參數(shù)映射,半封裝處理,面向?qū)ο笏季S將各模塊組裝到一起.