前言
上一篇分析Mybatis是如何載入解析XML檔案的,本篇緊接上文,分析Mybatis的剩餘兩個階段:代理封裝和SQL執行。
正文
代理封裝
Mybatis有兩種方式呼叫Mapper介面:
private static SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
// 第一種
try (SqlSession session = sqlMapper.openSession(TransactionIsolationLevel.SERIALIZABLE)) {
Blog blog = session.selectOne("org.apache.ibatis.domain.blog.mappers.BlogMapper.selectBlogWithPostsUsingSubSelect", 1);
}
// 第二種
try (SqlSession session = sqlMapper.openSession()) {
AuthorMapper mapper = session.getMapper(AuthorMapper.class);
Author author = mapper.selectAuthor(101);
}
從上面程式碼可以看到無論是哪一種首先都要建立SqlSessionFactory物件,然後通過這個物件拿到SqlSession物件。在早期版本中只能通過該物件的增刪改呼叫Mapper介面,很明顯這種方式可讀性很差,難以維護,寫起來也複雜,所以後面谷歌開始維護Mybatis後,重新封裝提供了第二種方式直接呼叫Mapper介面。不過本質上第二種是在第一種的基礎之上實現的,所以下面就以第二種為主進行分析,進入到getMapper方法:
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
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);
}
}
mapperRegistry物件在上一篇分析過,是在解析xml中的mapper節點時註冊進去的,而這個物件中快取了Mapper介面和對應的代理工廠的對映,所以getMapper的核心就是通過這個工廠去建立代理物件:
public T newInstance(SqlSession sqlSession) {
//每次呼叫都會建立新的MapperProxy物件
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
然後通過Mapper介面呼叫時首先就會呼叫到MapperProxy的invoke方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {//如果是Object本身的方法不增強
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//從快取中獲取mapperMethod物件,如果快取中沒有,則建立一個,並新增到快取中
final MapperMethod mapperMethod = cachedMapperMethod(method);
//呼叫execute方法執行sql
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
首先從快取中拿到MapperMethod物件,這個物件封裝了SQL語句的型別、名稱空間、入參、返回型別等資訊,然後通過它的execute方法呼叫SqlSession的增刪查改方法:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//根據sql語句型別以及介面返回的引數選擇呼叫不同的
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()) {//返回值為void
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {//返回值為集合或者陣列
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {//返回值為map
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {//返回值為遊標
result = executeForCursor(sqlSession, args);
} else {//處理返回為單一物件的情況
//通過引數解析器解析解析引數
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional() &&
(result == null || !method.getReturnType().equals(result.getClass()))) {
result = OptionalUtil.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
上文說過SqlSession本質上是門面模式的體現,其本質上是通過Executor執行器元件實現的,在該元件中定義了所有訪問資料庫的方法:
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//從configuration中獲取要執行的sql語句的配置資訊
MappedStatement ms = configuration.getMappedStatement(statement);
//通過executor執行語句,並返回指定的結果集
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();
}
}
而Executor物件是在獲取SqlSession時建立的:
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//獲取mybatis配置檔案中的environment物件
final Environment environment = configuration.getEnvironment();
//從environment獲取transactionFactory物件
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
//建立事務物件
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//根據配置建立executor
final Executor executor = configuration.newExecutor(tx, execType);
//建立DefaultSqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);
} 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();
}
}
TransactionFactory是我們在xml中配置的transactionManager屬性,可選的屬性有JDBC和Managed,然後根據我們的配置建立事務物件,之後才是建立Executor物件。
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
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);
}
//如果有<cache>節點,通過裝飾器,新增二級快取的能力
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
//通過interceptorChain遍歷所有的外掛為executor增強,新增外掛的功能
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
Executor有三個基本的實現類:
- BatchExecutor:批處理執行器,執行批量更新、插入等操作。
- ReuseExecutor:可重用執行器,快取並重用Statement(Statement、PreparedStatement、CallableStatement)。
- SimpleExecutor:預設使用的執行器,每次執行都會建立 新的Statement。
這三個執行器都繼承了自抽象的BaseExecutor,同時如果開啟了二級快取功能,在這裡還會裝飾一個CachingExecutor為其新增二級快取的能力。另外還要注意在這段程式碼的最後還有攔截器進行了包裝,也就是擴充套件外掛的實現 ,關於這部分內容在一篇進行分析。
SQL執行
二級快取的程式碼很簡單,這裡直接略過,所以直接進入到BaseExecutor.query方法:
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//獲取sql語句資訊,包括佔位符,引數等資訊
BoundSql boundSql = ms.getBoundSql(parameter);
//拼裝快取的key值
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {//檢查當前executor是否關閉
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {//非巢狀查詢,並且FlushCache配置為true,則需要清空一級快取
clearLocalCache();
}
List<E> list;
try {
queryStack++;//查詢層次加一
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;//查詢以及快取
if (list != null) {
//針對呼叫儲存過程的結果處理
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//快取未命中,從資料庫載入資料
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {//延遲載入處理
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {//如果當前sql的一級快取配置為STATEMENT,查詢完既清空一集快取
// issue #482
clearLocalCache();
}
}
return list;
}
首先從一級快取localCache裡面拿,如果沒有,才真正地訪問資料庫,並將返回結果存入到一級快取中。
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);//在快取中新增佔位符
try {
//呼叫抽象方法doQuery,方法查詢資料庫並返回結果,可選的實現包括:simple、reuse、batch
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);//在快取中刪除佔位符
}
localCache.putObject(key, list);//將真正的結果物件新增到一級快取
if (ms.getStatementType() == StatementType.CALLABLE) {//如果是呼叫儲存過程
localOutputParameterCache.putObject(key, parameter);//快取輸出型別結果引數
}
return list;
}
這裡的doQuery是子類實現的,即模板模式,以SimpleExecutor為例:
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();//獲取configuration物件
//建立StatementHandler物件,
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//StatementHandler物件建立stmt,並使用parameterHandler對佔位符進行處理
stmt = prepareStatement(handler, ms.getStatementLog());
//通過statementHandler物件呼叫ResultSetHandler將結果集轉化為指定物件返回
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
通讀這裡的程式碼我們可以發現,Executor本身是不會訪問到資料庫,而是作為指揮官,指揮三個小弟幹事:
- StatementHandler:建立PreparedStatement、Statement和CallableStatement物件。
- ParameterHandler:在StatementHandler建構函式中建立,對預編譯的 SQL 語句進行引數設定。
- ResultSetHandler:在StatementHandler建構函式中建立,對資料庫返回的結果集(ResultSet)進行封裝,返回使用者指定的實體型別。
上面三個物件都是在configuration.newStatementHandler方法中建立的,然後呼叫prepareStatement拿到合適的Statement,如果是預編譯的還會進行引數設定:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//獲取connection物件的動態代理,新增日誌能力;
Connection connection = getConnection(statementLog);
//通過不同的StatementHandler,利用connection建立(prepare)Statement
stmt = handler.prepare(connection, transaction.getTimeout());
//使用parameterHandler處理佔位符
handler.parameterize(stmt);
return stmt;
}
如果在DEBUG模式下拿到的Connection物件是ConnectionLogger,這就和第一篇的內容串聯起來了。之後再通過query方法呼叫execute執行SQL語句,並使用ResultSetHandler處理結果集:
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
//用於儲存結果集物件
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
//statment可能返回多個結果集物件,這裡先取出第一個結果集
ResultSetWrapper rsw = getFirstResultSet(stmt);
//獲取結果集對應resultMap,本質就是獲取欄位與java屬性的對映規則
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);//結果集和resultMap不能為空,為空丟擲異常
while (rsw != null && resultMapCount > resultSetCount) {
//獲取當前結果集對應的resultMap
ResultMap resultMap = resultMaps.get(resultSetCount);
//根據對映規則(resultMap)對結果集進行轉化,轉換成目標物件以後放入multipleResults中
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);//獲取下一個結果集
cleanUpAfterHandlingResultSet();//清空nestedResultObjects物件
resultSetCount++;
}
//獲取多結果集。多結果集一般出現在儲存過程的執行,儲存過程返回多個resultset,
//mappedStatement.resultSets屬性列出多個結果集的名稱,用逗號分割;
//多結果集的處理不是重點,暫時不分析
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
這裡最終就是通過反射模組以及Configuration類中的result相關配置進行結果對映:
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {//處理多結果集的巢狀對映
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
if (resultHandler == null) {//如果resultHandler為空,例項化一個人預設的resultHandler
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
//對ResultSet進行對映,對映結果暫存在resultHandler中
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
//將暫存在resultHandler中的對映結果,填充到multipleResults
multipleResults.add(defaultResultHandler.getResultList());
} else {
//使用指定的rusultHandler進行轉換
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
//呼叫resultset.close()關閉結果集
closeResultSet(rsw.getResultSet());
}
}
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
if (resultMap.hasNestedResultMaps()) {//處理有巢狀resultmap的情況
ensureNoRowBounds();
checkResultHandler();
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {//處理沒有巢狀resultmap的情況
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
//建立結果上下文,所謂的上下文就是專門在迴圈中快取結果物件的
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
//1.根據分頁資訊,定位到指定的記錄
skipRows(rsw.getResultSet(), rowBounds);
//2.shouldProcessMoreRows判斷是否需要對映後續的結果,實際還是翻頁處理,避免超過limit
while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
//3.進一步完善resultMap資訊,主要是處理鑑別器的資訊
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
//4.讀取resultSet中的一行記錄並進行對映,轉化並返回目標物件
Object rowValue = getRowValue(rsw, discriminatedResultMap);
//5.儲存對映結果物件
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
}
}
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
//4.1 根據resultMap的type屬性,例項化目標物件
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
//4.2 對目標物件進行封裝得到metaObjcect,為後續的賦值操作做好準備
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;//取得是否使用建構函式初始化屬性值
if (shouldApplyAutomaticMappings(resultMap, false)) {//是否使用自動對映
//4.3一般情況下 autoMappingBehavior預設值為PARTIAL,對未明確指定對映規則的欄位進行自動對映
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
}
//4.4 對映resultMap中明確指定需要對映的列
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
//4.5 如果沒有一個對映成功的屬性,則根據<returnInstanceForEmptyRow>的配置返回null或者結果物件
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}
- 自動對映
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
//獲取resultSet中存在的,但是ResultMap中沒有明確對映的列,填充至autoMapping中
List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
boolean foundValues = false;
if (!autoMapping.isEmpty()) {
//遍歷autoMapping,通過自動匹配的方式為屬性複製
for (UnMappedColumnAutoMapping mapping : autoMapping) {
//通過typeHandler從resultset中拿值
final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
// gcode issue #377, call setter on nulls (value is not 'found')
//通過metaObject給屬性賦值
metaObject.setValue(mapping.property, value);
}
}
}
return foundValues;
}
- 指定對映
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
//從resultMap中獲取明確需要轉換的列名集合
final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
boolean foundValues = false;
//獲取ResultMapping集合
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);//獲得列名,注意字首的處理
if (propertyMapping.getNestedResultMapId() != null) {
// the user added a column attribute to a nested result map, ignore it
//如果屬性通過另外一個resultMap對映,則忽略
column = null;
}
if (propertyMapping.isCompositeResult()//如果是巢狀查詢,column={prop1=col1,prop2=col2}
|| (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))//基本型別對映
|| propertyMapping.getResultSet() != null) {//巢狀查詢的結果
//獲得屬性值
Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
// issue #541 make property optional
//獲得屬性名稱
final String property = propertyMapping.getProperty();
if (property == null) {//屬性名為空跳出迴圈
continue;
} else if (value == DEFERED) {//屬性名為DEFERED,延遲載入的處理
foundValues = true;
continue;
}
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
// gcode issue #377, call setter on nulls (value is not 'found')
//通過metaObject為目標物件設定屬性值
metaObject.setValue(property, value);
}
}
}
return foundValues;
}
反射例項化物件的程式碼比較長,但邏輯都比較清晰,上面的關鍵流程程式碼也都加上了註釋,讀者可自行參照原始碼閱讀。
總結
Mybatis核心原理就分析完了,相比較Spring原始碼簡單了很多,但程式碼的優雅度和優秀的設計思想一點也不亞於Spring,也是非常值得我們好好學習掌握的。不過這3篇只是分析了Mybaits的核心執行原理,另外還有外掛怎麼擴充套件、攔截器會攔截哪些方法以及Mybatis和Spring的整合又是怎麼實現的呢?讀者們可以好好思考下,答案將在下一篇揭曉。