1. 程式人生 > >精盡MyBatis原始碼分析 - SqlSession 會話與 SQL 執行入口

精盡MyBatis原始碼分析 - SqlSession 會話與 SQL 執行入口

> 該系列文件是本人在學習 Mybatis 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋([Mybatis原始碼分析 GitHub 地址](https://github.com/liu844869663/mybatis-3)、[Mybatis-Spring 原始碼分析 GitHub 地址](https://github.com/liu844869663/spring)、[Spring-Boot-Starter 原始碼分析 GitHub 地址](https://github.com/liu844869663/spring-boot-starter))進行閱讀 > > MyBatis 版本:3.5.2 > > MyBatis-Spring 版本:2.0.3 > > MyBatis-Spring-Boot-Starter 版本:2.1.4 ## SqlSession會話與SQL執行入口 在前面一系列的文件中,已經詳細的介紹了 MyBatis 的初始化和執行 SQL 的過程,在執行 SQL 的過程中,還存在著一些疑問,例如其中一直提到的 SqlSession 會話在 MyBatis 中是如何被建立的?如何呼叫 Executor 執行器執行 SQL 的? 那麼接下來就關於上面兩個的兩個問題,一起來探討一下 MyBatis 中 SqlSession 會話的相關內容 先回顧一下[**《基礎支援層》**](https://www.cnblogs.com/lifullmoon/p/14014934.html)的**Binding模組**,每個`Mapper Interface`會有對應的`MapperProxyFactory`動態代理物件工廠,用於建立`MapperProxy`動態代理物件,Mapper 介面中的每個方法都有對應的`MapperMethod`物件,該物件會通過 SqlSession 會話執行資料操作 再來看到這張MyBatis整體圖:
在單獨使用 MyBatis 進行資料庫操作時,需要通過`SqlSessionFactoryBuilder`構建一個`SessionFactory`物件,然後通過它建立一個`SqlSession`會話進行資料庫操作 我們通常都會先呼叫`SqlSession`會話的`getMapper(Class mapper)`方法,為 Mapper 介面生成一個“實現類”(JDK動態代理物件),然後就可以通過它進行資料庫操作 ### 示例 ```java // <1> 構建 SqlSessionFactory 物件 Reader reader = Resources.getResourceAsReader("org/apache/ibatis/autoconstructor/mybatis-config.xml"); // <2> 預設 DefaultSqlSessionFactory 物件 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); // <3> 獲得 SqlSession 物件,預設 DefaultSqlSession 物件 SqlSession sqlSession = sqlSessionFactory.openSession(); // <4> 獲得 Mapper 物件 final AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class); // <5> 執行查詢 final Object subject = mapper.getSubject(1); ``` ### SqlSessionFactoryBuilder `org.apache.ibatis.session.SqlSessionFactoryBuilder`:構建SqlSessionFactory工廠類,裡面定義了許多build過載方法,主要分為處理Reader和InputStream兩種檔案資源物件,程式碼如下: ```java public class SqlSessionFactoryBuilder { public SqlSessionFactory build(Reader reader) { return build(reader, null, null); } public SqlSessionFactory build(Reader reader, String environment) { return build(reader, environment, null); } public SqlSessionFactory build(Reader reader, Properties properties) { return build(reader, null, properties); } /** * 構造 SqlSessionFactory 物件 * * @param reader Reader 物件 * @param environment 環境 * @param properties Properties 變數 * @return SqlSessionFactory 物件 */ public SqlSessionFactory build(Reader reader, String environment, Properties properties) { try { /* * <1> 建立 XMLConfigBuilder 物件 * 會生成一個 XPathParser,包含 Document 物件 * 會建立一個 Configuration 全域性配置物件 */ XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); /* * <2> 解析 XML 檔案並配置到 Configuration 全域性配置物件中 * <3> 建立 DefaultSqlSessionFactory 物件 */ return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { reader.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } } public SqlSessionFactory build(InputStream inputStream) { return build(inputStream, null, null); } public SqlSessionFactory build(InputStream inputStream, String environment) { return build(inputStream, environment, null); } public SqlSessionFactory build(InputStream inputStream, Properties properties) { return build(inputStream, null, properties); } public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } } public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); } } ``` 在[**《MyBatis初始化(一)之載入mybatis-config.xml》**](https://www.cnblogs.com/lifullmoon/p/14015009.html)中已經分析了,這裡就不再贅述,就是根據檔案資源建立Configuration全域性配置物件,然後構建一個DefaultSqlSessionFactory物件 ### DefaultSqlSessionFactory `org.apache.ibatis.session.defaults.DefaultSqlSessionFactory`:實現 SqlSessionFactory 介面,預設的 SqlSessionFactory 實現類 #### openSession方法 `openSession`方法,建立一個`DefaultSqlSession`物件,如下: ```java public class DefaultSqlSessionFactory implements SqlSessionFactory { private final Configuration configuration; public DefaultSqlSessionFactory(Configuration configuration) { this.configuration = configuration; } @Override public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); } @Override public SqlSession openSession(boolean autoCommit) { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit); } @Override public SqlSession openSession(ExecutorType execType) { return openSessionFromDataSource(execType, null, false); } @Override public SqlSession openSession(TransactionIsolationLevel level) { return openSessionFromDataSource(configuration.getDefaultExecutorType(), level, false); } @Override public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) { return openSessionFromDataSource(execType, level, false); } @Override public SqlSession openSession(ExecutorType execType, boolean autoCommit) { return openSessionFromDataSource(execType, null, autoCommit); } @Override public SqlSession openSession(Connection connection) { return openSessionFromConnection(configuration.getDefaultExecutorType(), connection); } @Override public SqlSession openSession(ExecutorType execType, Connection connection) { return openSessionFromConnection(execType, connection); } } ``` `openSession`有很多過載的方法,主要是提供以下幾種入參的支援: | 型別 | 引數名稱 | 預設值 | 描述 | | ------------------------- | ---------- | ------------------- | ------------------ | | boolean | autoCommit | false | 事務是否自動提交 | | ExecutorType | execType | ExecutorType.SIMPLE | Executor執行器型別 | | TransactionIsolationLevel | level | 無 | 事務隔離級別 | | java.sql.Connection | connection | 無 | 資料庫連線 | 內部直接呼叫`openSessionFromDataSource`私有方法,內部也需要呼叫`openSessionFromConnection`私有方法,如果存在`connection`入參,內部則直接呼叫`openSessionFromConnection`私有方法 #### openSessionFromDataSource方法 `openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit)`方法,用於建立一個`DefaultSqlSession`物件,方法如下: ```java 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(); } } ``` 1. 獲得`Environment`當前環境物件 2. 通過`getTransactionFactoryFromEnvironment`方法,從 Environment 環境物件中`TransactionFactory`物件,用於建立一個`Transaction`事務 3. 然後再建立一個`Executor`執行器物件 4. 根據全域性配置物件、執行器和事務是否自動提交建立一個`DefaultSqlSession`物件 #### openSessionFromConnection方法 `openSessionFromConnection(ExecutorType execType, Connection connection)`方法,用於建立一個`DefaultSqlSession`物件 和上面的方法邏輯相同,只不過它的入參直接傳入了一個Connection資料庫連線,方法如下: ```java private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) { try { // 獲得是否可以自動提交 boolean autoCommit; try { autoCommit = connection.getAutoCommit(); } catch (SQLException e) { // Failover to true, as most poor drivers // or databases won't support transactions autoCommit = true; } // 獲得 Environment 物件 final Environment environment = configuration.getEnvironment(); // 建立 Transaction 物件 final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); final Transaction tx = transactionFactory.newTransaction(connection); // 建立 Executor 物件 final Executor executor = configuration.newExecutor(tx, execType); // 建立 DefaultSqlSession 物件 return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } ``` 和上面的方法不同的是,建立`Transaction`事務物件時,傳入的引數直接是`Connection`資料庫連線物件 #### getTransactionFactoryFromEnvironment方法 `getTransactionFactoryFromEnvironment(Environment environment)`方法,用於建立一個`TransactionFactory`物件,在建立 SqlSessionFactory 時,就可以通過設定 Environment 的 DataSource 資料來源和 TransactionFactory 事務工廠來整合第三方資料來源和事務管理器,程式碼如下: ```java private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) { // 情況一,建立 ManagedTransactionFactory 物件 if (environment == null || environment.getTransactionFactory() == null) { return new ManagedTransactionFactory(); } // 情況二,使用 `environment` 中的 return environment.getTransactionFactory(); } ``` #### closeTransaction方法 `closeTransaction(Transaction tx)`方法,關閉事務,方法如下: ```java private void closeTransaction(Transaction tx) { if (tx != null) { try { tx.close(); } catch (SQLException ignore) { // Intentionally ignore. Prefer previous error. } } } ``` ### DefaultSqlSession `org.apache.ibatis.session.defaults.DefaultSqlSession`:實現 SqlSession 介面,預設的 SqlSession 實現類,呼叫 Executor 執行器,執行資料庫操作 #### 構造方法 ```java public class DefaultSqlSession implements SqlSession { /** * 全域性配置 */ private final Configuration configuration; /** * 執行器物件 */ private final Executor executor; /** * 是否自動提交事務 */ private final boolean autoCommit; /** * 是否發生資料變更 */ private boolean dirty; /** * Cursor 陣列 */ private List> cursorList; public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) { this.configuration = configuration; this.executor = executor; this.dirty = false; this.autoCommit = autoCommit; } } ``` #### select方法 執行資料庫查詢操作,提供了許多過載方法 ```java @Override public void select(String statement, Object parameter, ResultHandler handler) { select(statement, parameter, RowBounds.DEFAULT, handler); } @Override public void select(String statement, ResultHandler handler) { select(statement, null, RowBounds.DEFAULT, handler); } @Override public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) { try { MappedStatement ms = configuration.getMappedStatement(statement); executor.query(ms, wrapCollection(parameter), rowBounds, handler); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } @Override public T selectOne(String statement) { return this.selectOne(statement, null); } @Override public T selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. List list = this.selectList(statement, parameter); if (list.size() == 1) { return list.get(0); } else if (list.size() > 1) { throw new TooManyResultsException( "Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); } else { return null; } } @Override public Map selectMap(String statement, String mapKey) { return this.selectMap(statement, null, mapKey, RowBounds.DEFAULT); } @Override public Map selectMap(String statement, Object parameter, String mapKey) { return this.selectMap(statement, parameter, mapKey, RowBounds.DEFAULT); } @Override public Map selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) { // <1> 執行查詢 final List list = selectList(statement, parameter, rowBounds); // <2> 建立 DefaultMapResultHandler 物件 final DefaultMapResultHandler mapResultHandler = new DefaultMapResultHandler<>(mapKey, configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory()); // <3> 建立 DefaultResultContext 物件 final DefaultResultContext context = new DefaultResultContext<>(); // <4> 遍歷查詢結果 for (V o : list) { // 設定 DefaultResultContext 中 context.nextResultObject(o); // 使用 DefaultMapResultHandler 處理結果的當前元素 mapResultHandler.handleResult(context); } // <5> 返回結果 return mapResultHandler.getMappedResults(); } @Override public List selectList(String statement) { return this.selectList(statement, null); } @Override public List selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT); } @Override public List selectList(String statement, Object parameter, RowBounds rowBounds) { try { // <1> 獲得 MappedStatement 物件 MappedStatement ms = configuration.getMappedStatement(statement); // <2> 執行查詢 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(); } } ``` 上面有很多的資料庫查詢方法,主要分為以下幾種: - `select`:執行資料庫查詢操作,通過入參中的`ResultHandler`處理結果集,無返回結果 - `selectList`:執行資料庫查詢操作,返回List集合 - `selectOne`:呼叫`selectList`方法,執行資料庫查詢操作,最多隻能返回一條資料 - `selectMap`:呼叫`selectList`方法,執行資料庫查詢操作,通過`DefaultMapResultHandler`進行處理,將返回結果轉換成Map集合 可以看到通過Executor執行器的`query`方法執行查詢操作,可以回顧[**《SQL執行過程(一)之Executor》**](https://www.cnblogs.com/lifullmoon/p/14015111.html)中的內容 這裡會先呼叫`wrapCollection`方法對入參進行包裝(如果是集合型別) #### update方法 執行資料庫更新操作 ```java @Override public int insert(String statement) { return insert(statement, null); } @Override public int insert(String statement, Object parameter) { return update(statement, parameter); } @Override public int delete(String statement) { return update(statement, null); } @Override public int delete(String statement, Object parameter) { return update(statement, parameter); } @Override public int update(String statement) { return update(statement, null); } @Override public int update(String statement, Object parameter) { try { // <1> 標記 dirty ,表示執行過寫操作 dirty = true; // <2> 獲得 MappedStatement 物件 MappedStatement ms = configuration.getMappedStatement(statement); // <3> 執行更新操作 return executor.update(ms, wrapCollection(parameter)); } catch (Exception e) { throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } ``` `insert`和`delete`方法最終都是呼叫`update`方法,通過Executor執行器的`update`方法執行資料庫更新操作 這裡會先呼叫`wrapCollection`方法對入參進行包裝(如果是集合型別) #### wrapCollection方法 `wrapCollection(final Object object)`方法,將集合型別的引數包裝成`StrictMap`物件,方法如下: ```java public static class StrictMap extends HashMap { private static final long serialVersionUID = -5741767162221585340L; @Override public V get(Object key) { if (!super.containsKey(key)) { throw new BindingException( "Parameter '" + key + "' not found. Available parameters are " + this.keySet()); } return super.get(key); } } private Object wrapCollection(final Object object) { if (object instanceof Collection) { // 如果是集合,則新增到 collection 中 StrictMap map = new StrictMap<>(); map.put("collection", object); // 如果是 List ,則新增到 list 中 if (object instanceof List) { map.put("list", object); } return map; } else if (object != null && object.getClass().isArray()) { // 如果是 Array ,則新增到 array 中 StrictMap map = new StrictMap<>(); map.put("array", object); return map; } return object; } ``` #### getMapper方法 `getMapper(Class type)`方法,獲取Mapper介面的代理物件 ```java @Override public T getMapper(Class type) { return configuration.getMapper(type, this); } ``` 通過`Configuration`全域性配置物件獲取一個動態代理物件 實際通過`MapperRegistry`登錄檔獲取到該Mapper介面對應的`MapperProxyFactory`動態代理工廠,然後建立一個`MapperProxy`動態代理的例項物件 #### 其他方法 - `flushStatements()`:提交批處理 - `commit()`:提交事務 - `rollback()`:回滾事務 - `close()`:關閉當前會話 - `getConnection()`:獲取當前事務的資料庫連線 - `clearCache()`:清理一級快取 ### MapperMethod `org.apache.ibatis.binding.MapperMethod`:Mapper介面中定義的方法對應的Mapper方法,通過它來執行SQL 先來看看執行一個SQL的完整流程圖:
在[**《基礎支援層》**](https://www.cnblogs.com/lifullmoon/p/14014934.html)的**Binding模組**已經講過了相關的內容,例如看到前面**示例**的第`4`步,通過`DefaultSqlSession`的`getMapper`方法會執行以下操作: 1. 建立Mapper介面對應的`MapperProxyFactory`動態代理物件工廠 2. 通過這個工廠的`newInstance`方法會建立一個`MapperProxy`介面代理類,然後返回該Mapper介面的動態代理物件 當你呼叫這個介面的某個方法時,會進入這個`MapperProxy`代理類,我們來看到它的`invoke`方法是如何實現的,方法如下: ```java @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { // <1> 如果是 Object 定義的方法,直接呼叫 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (method.isDefault()) { // 是否有 default 修飾的方法 // 針對Java7以上版本對動態型別語言的支援 if (privateLookupInMethod == null) { return invokeDefaultMethodJava8(proxy, method, args); } else { return invokeDefaultMethodJava9(proxy, method, args); } } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } // <2.1> 獲得 MapperMethod 物件 final MapperMethod mapperMethod = cachedMapperMethod(method); // <2.2> 執行 MapperMethod 方法 return mapperMethod.execute(sqlSession, args); } ``` 首先獲取到方法對應的`MapperMethod`物件,然後通過該物件去執行資料庫的操作 #### execute方法 ```java public Object execute(SqlSession sqlSession, Object[] args) { // 根據 SqlCommand 的 Type 判斷應該如何執行 SQL 語句 Object result; switch (command.getType()) { case INSERT: { // <1> 獲取引數值與引數名的對映 Object param = method.convertArgsToSqlCommandParam(args); // <2> 然後通過 SqlSession 進行資料庫更新操作,並將受影響行數轉換為結果 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()) { // 無返回,且入參中有 ResultHandler 結果處理器 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()) { // 執行查詢,返回 Cursor 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 = Optional.ofNullable(result); } } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + command.getName()); } // 返回結果為 null ,並且返回型別為原始型別(基本型別),則丟擲 BindingException 異常 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; } ``` 根據 SqlCommand 的 Type 判斷應該如何執行 SQL 語句,型別分為`INSERT`、`UPDATE`、`DELETE`、`SELECT`和`FLUSH`(進行批處理)五種 前三種都屬於資料庫的更新操作,呼叫的是`DefaultSqlSession`的`update`方法,通過`rowCountResult(int rowCount)`將受影響行數轉換為返回物件 查詢語句的話就分為以下幾種情況: 1. 無返回,且入參中有 `ResultHandler` 結果處理器,則呼叫`executeWithResultHandler`方法,執行查詢,返回結果設定為null 2. 返回物件為多條資料,則呼叫`executeForMany`方法,執行查詢,返回列表 3. 返回物件為Map型別,則呼叫`executeForMap`方法,執行查詢,返回Map 4. 返回物件為Cursor型別,則呼叫`executeForCursor`方法,執行查詢,返回Cursor 5. 返回物件為單個物件,則呼叫`DefaultSqlSession`的`selectOne`方法,執行查詢,返回單個物件 上面執行資料庫操作前會先呼叫`convertArgsToSqlCommandParam`方法,獲取引數值與引數名的對映 #### convertArgsToSqlCommandParam方法 `convertArgsToSqlCommandParam(Object[] args)`方法,根據入參返回引數名稱與入參的對映,方法如下: ```java public Object convertArgsToSqlCommandParam(Object[] args) { return paramNameResolver.getNamedParams(args); } ``` 在[**《基礎支援層》**](https://www.cnblogs.com/lifullmoon/p/14014934.html)的**反射模組**的**ParamNameResolver**小節已經分析過該方法,可以跳過去看看 #### rowCountResult方法 `rowCountResult(int rowCount)`方法,將受影響行數轉換為結果,方法如下: ```java private Object rowCountResult(int rowCount) { final Object result; if (method.returnsVoid()) { result = null; } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) { result = rowCount; } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) { result = (long) rowCount; } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) { result = rowCount > 0; } else { throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType()); } return result; } ``` #### executeWithResultHandler方法 `executeWithResultHandler(SqlSession sqlSession, Object[] args)`方法,通過入參中定義的ResultHandler結果處理器執行查詢,方法如下: ```java private void executeWithResultHandler(SqlSession sqlSession, Object[] args) { // <1> 獲得 MappedStatement 物件 MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName()); /* * <2> 配置校驗 * 因為入參定義了 ResultHandler 結果處理器,所以如果不是儲存過程,且沒有配置返回結果的 Java Type,則會丟擲異常 */ if (!StatementType.CALLABLE.equals(ms.getStatementType()) && void.class.equals(ms.getResultMaps().get(0).getType())) { throw new BindingException( "method " + command.getName() + " needs either a @ResultMap annotation, a @ResultType annotation," + " or a resultType attribute in XML so a ResultHandler can be used as a parameter."); } // <3> 獲取引數名稱與入參的對映 Object param = method.convertArgsToSqlCommandParam(args); // <4> 執行資料庫查詢操作 if (method.hasRowBounds()) { // <4.1> 入參定義了 RowBounds 分頁物件 // <4.1.1> 獲取入參定義了 RowBounds 分頁物件 RowBounds rowBounds = method.extractRowBounds(args); // <4.1.2> 執行查詢 sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args)); } else { // <4.2> 執行查詢 sqlSession.select(command.getName(), param, method.extractResultHandler(args)); } } ``` 1. 獲得 `MappedStatement` 物件 2. 配置校驗,因為入參定義了 `ResultHandler` 結果處理器,所以如果不是儲存過程,且沒有配置返回結果的 Java Type,則會丟擲異常 3. 獲取引數名稱與入參的對映 4. 執行資料庫查詢操作 1. 入參定義了 `RowBounds` 分頁物件,則獲取該物件,然後執行查詢,傳入分析物件 2. 沒有定義則執行執行查詢 #### executeForMany方法 `executeForMany(SqlSession sqlSession, Object[] args)`方法,執行查詢,返回列表,方法如下: ```java private Object executeForMany(SqlSession sqlSession, Object[] args) { List result; // 獲取引數名稱與入參的對映 Object param = method.convertArgsToSqlCommandParam(args); // 執行資料庫查詢操作 if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); // 執行查詢,返回 List 集合 result = sqlSession.selectList(command.getName(), param, rowBounds); } else { // 執行查詢,返回 List 集合 result = sqlSession.selectList(command.getName(), param); } // issue #510 Collections & arrays support // 封裝 Array 或 Collection 結果 if (!method.getReturnType().isAssignableFrom(result.getClass())) { // 如果不是 List 集合型別 if (method.getReturnType().isArray()) { // 將 List 轉換成 Array 陣列型別的結果 return convertToArray(result); } else { // 轉換成其他 Collection 集合型別的結果 return convertToDeclaredCollection(sqlSession.getConfiguration(), result); } } // 直接返回 List 集合型別的結果 return result; } ``` 1. 獲取引數名稱與入參的對映 2. 執行資料庫查詢操作,獲取到 List 集合結果 3. 根據返回結果的型別進行轉換 1. 如果是 Array 陣列型別,則將 List 轉換成 Array 陣列型別的結果 2. 如果是其他集合型別,則將 List 轉換成其他 Collection 集合型別的結果 3. 否則直接返回 List 集合型別的結果 #### executeForMap方法 `executeForMap(SqlSession sqlSession, Object[] args)`方法,執行查詢,返回Map,方法如下: ```java private Map executeForMap(SqlSession sqlSession, Object[] args) { Map result; // 獲取引數名稱與入參的對映 Object param = method.convertArgsToSqlCommandParam(args); // 執行 SELECT 操作 if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); // 執行查詢,返回 Map 集合 result = sqlSession.selectMap(command.getName(), param, method.getMapKey(), rowBounds); } else { // 執行查詢,返回 Map 集合 result = sqlSession.selectMap(command.getName(), param, method.getMapKey()); } return result; } ``` 1. 獲取引數名稱與入參的對映 2. 執行查詢,返回 Map 集合 ### 總結 本分分析了 SqlSession 會話在 MyBatis 中是如何被建立,如何獲取到 Mapper 介面的動態代理物件,通過該動態代理物件是如何執行 SQL 的 1. `SqlSessionFactoryBuilder`構造器提供`build`方法,根據`mybatis-config.xml`配置檔案對 MyBatis 進行初始化,生成`Configuration`物件,用於構建一個`DefaultSqlSessionFactory`物件 2. 通過`1`生成的 SqlSession 工廠物件,我們可以構建一個`DefaultSqlSession`會話物件,通過這個會話物件的`getMapper(Class mapper)`方法,可以為 Mapper 介面建立一個`動態代理物件`(JDK動態代理),其動態代理類為`MapperProxy`物件 3. 呼叫 Mapper 介面的某個方法時,會進入相應的`MapperProxy`代理類,根據方法對應的`MapperMethod`物件,執行資料庫操作 4. 執行資料庫相關操作,呼叫的就是上面的`DefaultSqlSession`會話物件的相關方法,該會話內部則會通過`Executor`執行器去執行資料庫的相關操作,並返回執行結果 好了,對於 MyBatis 整體上所有的內容已經全部分析完了,相信大家對 MyBatis 有了一個全面認識,其中肯定有不對或者迷惑的地方,歡迎指正!!!感謝大家的閱讀!!!:smile::smile::smile: > 參考文章:**芋道原始碼**[《精盡 MyBatis 原始碼分析》](http://svip.iocoder.cn/categories/M