淺談Mybatis SqlSession執行流程
Mybatis執行SQL流程
在看源碼之前,我們需要瞭解一些基本知識,如果您沒有閱讀Mybatis SqlSessionFactory 初始化原理,可以先閱讀Mybatis SqlSessionFactory 初始化原理這篇文章,這用更有助於我們理解接下來的文章
在看源碼之前,我們需要瞭解一些基本知識
SqlSession
SqlSession是一個接口,它有兩個實現類:
– DefaultSqlSession:默認實現類
– SqlSessionManager:已經棄用的實現類,所以我們不需要關註他
SqlSession是與數據庫交互的頂層類,通常與ThreadLocal綁定,一個會話使用一個SqlSession,SqlSession是線程不安全的,使用完畢需要close()
public class DefaultSqlSession implements SqlSession { private final Configuration configuration; private final Executor executor; }
SqlSession中最重要的兩個變量:
– Configuration:核心配置類,也是初始化時傳過來的
– Executor:實際執行SQL的執行器
Executor
Executor是一個接口,有三個實現類
– BatchExecutor 重用語句,並執行批量更新
– ReuseExecutor 重用預處理語句prepared statements
– SimpleExecutor 普通的執行器,默認使用
瞭解完基本知識後,我們接著往下看代碼。
當創建完SqlSessionFactory後,就可以創建SqlSession,然後使用SqlSession進行增刪改查:
// 1. 讀取配置文件,讀成字節輸入流,註意:現在還沒解析 InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); // 2. 解析配置文件,封裝Configuration對象 創建DefaultSqlSessionFactory對象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); List<Object> objects = sqlSession.selectList("namespace.id");
我們先去看openSession()方法,創建瞭SqlSession
//6. 進入openSession方法 @Override public SqlSession openSession() { //getDefaultExecutorType()傳遞的是SimpleExecutor // level:數據庫事物級別,null return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); } //7. 進入openSessionFromDataSource。 //ExecutorType 為Executor的類型,TransactionIsolationLevel為事務隔離級別,autoCommit是否開啟事務 //openSession的多個重載方法可以指定獲得的SeqSession的Executor類型和事務的處理 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { // 獲得 Environment 對象 final Environment environment = configuration.getEnvironment(); // 創建 Transaction 對象 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) { // 如果發生異常,則關閉 Transaction 對象 closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
通過源碼可以清晰的看到,會話工廠創建瞭Environment,Transaction,Executor,DefaultSqlSession對象,並且對於會話對象來說,他的autoCommit默認為false,默認不自動提交。
然後我回到原來的代碼,接著就需要使用SqlSession進行增刪改查操作瞭
所以我們進入selectList()查看
//8.進入selectList方法,多個重載方法 @Override public <E> List<E> selectList(String statement) { return this.selectList(statement, null); } @Override public <E> List<E> selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT); } @Override public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { // 獲得 MappedStatement 對象 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(); } }
selectList有多個重載方法,進入到最終方法後,我們可以看到它做瞭兩件事
- 通過statementId,從Configuration中取MappedStatement對象,就是存放瞭sql語句,返回值類型,輸入值類型的對象
- 然後委派Executor執行器去執行具體的增刪改查方法
所以,對於實際JDBC的操作,我們還需要進入Executor中查看
Mybatis之Executor
我們繼續從剛剛selectList源碼中,進入Executor查看
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
//此方法在SimpleExecutor的父類BaseExecutor中實現 @Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { //根據傳入的參數動態獲得SQL語句,最後返回用BoundSql對象表示 BoundSql boundSql = ms.getBoundSql(parameter); //為本次查詢創建緩存的Key CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); // 查詢 return query(ms, parameter, rowBounds, resultHandler, key, boundSql); }
拆分成瞭三大步:
(1)先調用MappedStatement的getBoundSql方法,獲取解析後的SQL語句,解析工作是由SqlSourceBuilder完成的
什麼叫解析後的SQL語句呢?因為Mybatis編寫SQL語句時,會用到動態SQL,比如#{}占位符,這種占位符JDBC是不認識的,所以需要將其轉換成?占位符,並且將其內部的字段名存儲起來,後面填充參數的時候好使用反射獲取值。
/** * 執行解析原始 SQL ,成為 SqlSource 對象 * * @param originalSql 原始 SQL * @param parameterType 參數類型 * @param additionalParameters 附加參數集合。可能是空集合,也可能是 {@link org.apache.ibatis.scripting.xmltags.DynamicContext#bindings} 集合 * @return SqlSource 對象 */ public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) { // 創建 ParameterMappingTokenHandler 對象 ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters); // 創建 GenericTokenParser 對象 GenericTokenParser parser = new GenericTokenParser("#{", "}", handler); // 執行解析 String sql = parser.parse(originalSql); // 創建 StaticSqlSource 對象 return new StaticSqlSource(configuration, sql, handler.getParameterMappings()); }
上面代碼就可以看到,會將拆分#{和},進行解析
(2)根據查詢條件,創建緩存key,用來接下來去緩存查找是否有已經執行過的結果
(3)調用重載query()方法
接著我們進入重載方法查看:
@Override 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()); // 已經關閉,則拋出 ExecutorException 異常 if (closed) { throw new ExecutorException("Executor was closed."); } // 清空本地緩存,如果 queryStack 為零,並且要求清空本地緩存。 if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List<E> list; try { // queryStack + 1 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 - 1 queryStack--; } if (queryStack == 0) { // 執行延遲加載 for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 // 清空 deferredLoads deferredLoads.clear(); // 如果緩存級別是 LocalCacheScope.STATEMENT ,則進行清理 if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list; }
主要的邏輯:
- 從一級緩存取數據,如果有直接使用緩存的進行接下來的操作
- 如果沒有,從數據庫查詢
進入queryFromDatabase()方法:
// 從數據庫中讀取操作 private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> list; // 在緩存中,添加占位對象。此處的占位符,和延遲加載有關,可見 `DeferredLoad#canLoad()` 方法 localCache.putObject(key, EXECUTION_PLACEHOLDER); try { // 執行讀操作 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; } @Override 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(); // 傳入參數創建StatementHanlder對象來執行查詢 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 創建jdbc中的statement對象 stmt = prepareStatement(handler, ms.getStatementLog()); // 執行 StatementHandler ,進行讀操作 return handler.query(stmt, resultHandler); } finally { // 關閉 StatementHandler 對象 closeStatement(stmt); } }
通過代碼可以看到,對於實際與JDBC交互的代碼,Executor也懶得搞,又像SqlSession一樣,委派給小弟StatementHandler瞭。
Mybatis之StatementHandler
我們從剛剛的Executor的代碼查看
@Override 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(); // 傳入參數創建StatementHanlder對象來執行查詢 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 創建jdbc中的statement對象 stmt = prepareStatement(handler, ms.getStatementLog()); // 執行 StatementHandler ,進行讀操作 return handler.query(stmt, resultHandler); } finally { // 關閉 StatementHandler 對象 closeStatement(stmt); } }
可以看到,這裡創建完StatementHandler後,回調用prepareStatement()方法,用來創建Statement對象
我們進入prepareStatement方法中查看
// 初始化 StatementHandler 對象 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; // 獲得 Connection 對象 Connection connection = getConnection(statementLog); // 創建 Statement 或 PrepareStatement 對象 stmt = handler.prepare(connection, transaction.getTimeout()); // 設置 SQL 上的參數,例如 PrepareStatement 對象上的占位符 handler.parameterize(stmt); return stmt; } @Override public void parameterize(Statement statement) throws SQLException { //使用ParameterHandler對象來完成對Statement的設值 parameterHandler.setParameters((PreparedStatement) statement); }
這裡可以看到,它實際是使用ParameterHandler來設置Statement的參數
@Override public void setParameters(PreparedStatement ps) { ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); // 遍歷 ParameterMapping 數組 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings != null) { for (int i = 0; i < parameterMappings.size(); i++) { // 獲得 ParameterMapping 對象 ParameterMapping parameterMapping = parameterMappings.get(i); if (parameterMapping.getMode() != ParameterMode.OUT) { // 獲得值 Object value; String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } // 獲得 typeHandler、jdbcType 屬性 TypeHandler typeHandler = parameterMapping.getTypeHandler(); JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null) { jdbcType = configuration.getJdbcTypeForNull(); } // 設置 ? 占位符的參數 try { typeHandler.setParameter(ps, i + 1, value, jdbcType); } catch (TypeException | SQLException e) { throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); } } } } }
這段代碼的主要目的,就是獲取入參,然後根據值,來設置?占位符的參數
TypeHandler是具體進行參數設置的對象
所以handler.prepare(connection, transaction.getTimeout());方法,就是使用ParameterHandler來對占位符位置的參數進行值設置
然後我們回到Executor,查看handler.query()方法
@Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; // 執行查詢 ps.execute(); // 處理返回結果 return resultSetHandler.handleResultSets(ps); }
代碼很簡單,這裡直接使用JDBC的PreparedStatement來進行SQL執行,然後使用ResultSetHandler進行結果數據封裝處理。
進入ResultSetHandler
@Override public List<Object> handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); // 多 ResultSet 的結果集合,每個 ResultSet 對應一個 Object 對象。而實際上,每個 Object 是 List<Object> 對象。 // 在不考慮存儲過程的多 ResultSet 的情況,普通的查詢,實際就一個 ResultSet ,也就是說,multipleResults 最多就一個元素。 final List<Object> multipleResults = new ArrayList<>(); int resultSetCount = 0; // 獲得首個 ResultSet 對象,並封裝成 ResultSetWrapper 對象 ResultSetWrapper rsw = getFirstResultSet(stmt); // 獲得 ResultMap 數組 // 在不考慮存儲過程的多 ResultSet 的情況,普通的查詢,實際就一個 ResultSet ,也就是說,resultMaps 就一個元素。 List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); // 校驗 while (rsw != null && resultMapCount > resultSetCount) { // 獲得 ResultMap 對象 ResultMap resultMap = resultMaps.get(resultSetCount); // 處理 ResultSet ,將結果添加到 multipleResults 中 handleResultSet(rsw, resultMap, multipleResults, null); // 獲得下一個 ResultSet 對象,並封裝成 ResultSetWrapper 對象 rsw = getNextResultSet(stmt); // 清理 cleanUpAfterHandlingResultSet(); // resultSetCount ++ resultSetCount++; } // 因為 `mappedStatement.resultSets` 隻在存儲過程中使用,本系列暫時不考慮,忽略即可 // ··· // 如果是 multipleResults 單元素,則取首元素返回 return collapseSingleResultList(multipleResults); } // 處理 ResultSet ,將結果添加到 multipleResults 中 private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException { try { // 暫時忽略,因為隻有存儲過程的情況,調用該方法,parentMapping 為非空 if (parentMapping != null) { handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping); } else { // 如果沒有自定義的 resultHandler ,則創建默認的 DefaultResultHandler 對象 if (resultHandler == null) { // 創建 DefaultResultHandler 對象 DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory); // 處理 ResultSet 返回的每一行 Row handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null); // 添加 defaultResultHandler 的處理的結果,到 multipleResults 中 multipleResults.add(defaultResultHandler.getResultList()); } else { // 處理 ResultSet 返回的每一行 Row handleRowValues(rsw, resultMap, resultHandler, rowBounds, null); } } } finally { // issue #228 (close resultsets) // 關閉 ResultSet 對象 closeResultSet(rsw.getResultSet()); } }
代碼比較多,實際最重要的代碼就是
// 添加 defaultResultHandler 的處理的結果,到 multipleResults 中 multipleResults.add(defaultResultHandler.getResultList());
將處理後的結果封裝到集合中返回,這樣基本Mybatis邏輯就走完瞭.
我們來回顧一下,都用到瞭哪些類
簡單總結
SqlSessionFactoryBuilder:
- 解析核心配置文件,創建Configuration
- XMLConfigBuilder.parse():解析核心配置文件
- XMLMapperBuilder.parse():解析映射配置文件MappedStatement
- 創建SqlSessionFactory,默認創建DefaultSqlSessionFactory
SqlSessionFactory:
- openSession():構建Executor,SqlSession等
SqlSession:
- 根據statementId獲取MappedStatement
- 委派給Executor執行器執行
Executor:
- 使用SqlSourceBuilder,將SQL解析成JDBC認可的
- 查詢緩存,是否存在結果
- 結果不存在,委派給StatementHandler處理器
StatementHandler:
- PreparedStatement:處理參數,將參數賦值到占位符上
- TypeHandler:具體設置值的類
- ResultSetHandler:封裝結果集,封裝成設置的返回值類型
- TypeHandler:根據結果集,取出對應列
到此這篇關於淺談Mybatis SqlSession執行流程的文章就介紹到這瞭,更多相關Mybatis SqlSession執行流程內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!