上一篇分析Mybatis是如何加載解析XML文件的,本篇緊接上文,分析Mybatis的剩餘兩個階段:代理封裝和SQL執行。java
Mybatis有兩種方式調用Mapper接口:sql
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的核心就是經過這個工廠去建立代理對象:apache
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執行器組件實現的,在該組件中定義了全部訪問數據庫的方法:session
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時建立的:mybatis
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對象。app
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有三個基本的實現類:dom
這三個執行器都繼承了自抽象的BaseExecutor,同時若是開啓了二級緩存功能,在這裏還會裝飾一個CachingExecutor爲其添加二級緩存的能力。另外還要注意在這段代碼的最後還有攔截器進行了包裝,也就是擴展插件的實現 ,關於這部份內容在一篇進行分析。
二級緩存的代碼很簡單,這裏直接略過,因此直接進入到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自己是不會訪問到數據庫,而是做爲指揮官,指揮三個小弟幹事:
上面三個對象都是在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的整合又是怎麼實現的呢?讀者們能夠好好思考下,答案將在下一篇揭曉。