MyBatis原始碼分析之核心處理層

?沙漠駱駝發表於2020-08-26

mybatis與資料庫進行互動有兩種方式,一種傳統方式,一種mapper代理方式。通過對兩種方式的分析我們需要掌握以下內容:

  • 傳統方式
  1. MyBatis如何載入解析配置檔案?
  2. MyBatis如何解析SQL、設定引數以及執行SQL的?
  3. MyBatis如何封裝返回結果集?
  • mapper代理方式
  1. MyBatis底層如何產生代理物件?
  2. 當代理物件呼叫方法時它又是如何執行到底層的JDBC程式碼的?

1 傳統方式原始碼剖析

類似於Spring、MyBatis等靈活性和可擴充性都很高的開源框架都提供了很多配置項,開發人員需要在使用時提供相應的配置資訊,實現相應的需求。MyBatis中的配置檔案主要有兩個,分別是mybatis-config.xml和對映配置檔案。現在主流的配置方式除了使用XML配置檔案,還會配合註解進行配置。

1.1 初始化流程

Mybatis初始化的主要工作就是載入並解析mybatis-config.xml配置檔案、對映配置檔案以及相關的註解資訊。在引入了MyBatis依賴的專案中我們可以通過以下兩行程式碼執行初始化操作。

InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//這一行程式碼就是初始化的開始
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);

Mybatis的初始化入口就是SqlSessionFactoryBuilder.build()方法。再來看看build()方法裡做了什麼。

    // 1.我們最初呼叫的build
    public SqlSessionFactory build(InputStream inputStream) {
        //呼叫了過載方法
        return build(inputStream, null, null);
    }
    
    // 2.呼叫的過載方法
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
            // 建立 XMLConfigBuilder, XMLConfigBuilder是專門解析mybatis的配置檔案的類
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            // 執行 XML 解析
            // 建立 DefaultSqlSessionFactory 物件。parse()方法返回的是Configuration物件。
            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.
            }
        }
    }

從上面的原始碼可以看出,build()方法很簡單,只是呼叫了一下過載的build()方法,具體細節可以看註釋。接下來我們開始分析XMLConfigBuilder的parse()方法以及Configuration物件。MyBatis在初始化的時候,會將MyBatis的配置資訊全部載入到記憶體中,使用Configuration物件例項來儲存。Configuration物件的結構和xml配置檔案幾乎相同,也就是說XML檔案中的properties(屬性)、setting(設定)、typeAliases(型別別名)和typeHandlers(型別處理器)等配置標籤在Configuration物件中都有對應的屬性來封裝它們。也就是說,初始化配置檔案資訊的本質就是建立Configuration物件,將解析的XML資料封裝到Configuration內部屬性中。

    /**
     * 解析 XML 成 Configuration 物件。
     *
     * @return Configuration 物件
     */
    public Configuration parse() {
        // 若已解析,丟擲 BuilderException 異常
        if (parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        // 標記已解析
        parsed = true;
        ///parser是XPathParser解析器物件,讀取節點內資料,<configuration>是MyBatis配置檔案中的頂層標籤
        // 解析 XML configuration 節點
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }

    /**
     * 解析 XML
     *
     * 具體 MyBatis 有哪些 XML 標籤,參見 《XML 對映配置檔案》http://www.mybatis.org/mybatis-3/zh/configuration.html
     *
     * @param root 根節點
     */
    private void parseConfiguration(XNode root) {
        try {
            //issue #117 read properties first
            // 解析 <properties /> 標籤
            propertiesElement(root.evalNode("properties"));
            // 解析 <settings /> 標籤
            Properties settings = settingsAsProperties(root.evalNode("settings"));
            // 載入自定義的 VFS 實現類
            loadCustomVfs(settings);
            // 解析 <typeAliases /> 標籤
            typeAliasesElement(root.evalNode("typeAliases"));
            // 解析 <plugins /> 標籤
            pluginElement(root.evalNode("plugins"));
            // 解析 <objectFactory /> 標籤
            objectFactoryElement(root.evalNode("objectFactory"));
            // 解析 <objectWrapperFactory /> 標籤
            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            // 解析 <reflectorFactory /> 標籤
            reflectorFactoryElement(root.evalNode("reflectorFactory"));
            // 賦值 <settings /> 到 Configuration 屬性
            settingsElement(settings);
            // read it after objectFactory and objectWrapperFactory issue #631
            // 解析 <environments /> 標籤
            environmentsElement(root.evalNode("environments"));
            // 解析 <databaseIdProvider /> 標籤
            databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            // 解析 <typeHandlers /> 標籤
            typeHandlerElement(root.evalNode("typeHandlers"));
            // 解析 <mappers /> 標籤 
            mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }

從原始碼中可以看到,程式是通過parseConfiguration()實現了對mybatis-config.xml配置檔案中的每個節點進行解析的,每個節點的解析過程都封裝成了一個單獨的方法,程式碼清爽簡潔。

程式在解析<mappers>節點時,會建立XMLMapperBuilder物件載入對映檔案,如果對映配置檔案配置的是相應的Mapper介面,也會載入相應的Mapper介面,解析其中的註解並向MapperRegistry完成註冊。在解析<mapper>標籤的時,程式也會完成對SQL的解析,並將解析後的SQL節點資訊封裝成MappedStatement物件(描述一條SQL語句),然後存入Configuration物件中的一個HashMap型別的屬性mappedStatements中,map的key即是${namespace}.${id}(全限定類名 + 方法名),value對應的MappedStatement物件。

在解析SQL節點中,有兩個難點,一個是解析動態SQL,另一個是解析<include>節點,此處只講整體解析流程,這兩個知識點後面以面試題形式單獨文章分析。

    /**
     * MappedStatement 對映
     *
     * KEY:`${namespace}.${id}`
     */
    protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");

MappedStatement包含了SQL節點的很多屬性,其中比較重要的欄位如下:

/**
 * 對映的語句,每個 <select />、<insert />、<update />、<delete /> 對應一個 MappedStatement 物件
 *
 * @author Clinton Begin
 */
public final class MappedStatement {

    /**
     * 節點中的id屬性(包括名稱空間字首)
     */
    private String resource;
    
    /**
     * SqlSource 物件,對應一條SQL語句
     */
    private SqlSource sqlSource;
    
    /**
     * SQL 語句型別(INSERT、UPDATE、DELETE、SELECT或FLUSH)
     */
    private SqlCommandType sqlCommandType;

    // 其他欄位
    ··· ···
}

解析標籤的細節這裡不一一介紹,感興趣可以閱讀《MyBatis技術內幕》。以上,便是MyBatis載入解析配置檔案的整體流程。

1.2 執行SQL流程

初始化完成以後,程式就可以準備接收引數執行SQL了,在分析MyBatis執行SQL流程之前,需要先介紹兩個介面:SqlSession介面和Executor介面。

  1. SqlSession
    SqlSession是一個介面,它有兩個實現類:DefaultSqlSession(預設)和SqlSessionManager(棄用,不做介紹)。SqlSession是MyBatis中用於和資料庫互動的頂層類,通常將它與ThreadLocal繫結,因為在控制事務時需要保證一個事務裡的資料庫操作都是在一個連結裡執行的,一個會話使用一個SqlSession,並且在使用完畢後需要close。
public class DefaultSqlSession implements SqlSession {

    private final Configuration configuration;
    private final Executor executor;
    ··· ···
}    

SqlSession中的兩個最重要的引數,configuration與初始化時的相同,Executor為執行器。

  1. Executor
    Executor也是一個介面,其中定義了資料庫操作的基本方法,常用的實現類有三個:BatchExecutor(重用語句並執行批量更新)、ReuseExecutor(重用預處理語句preparedStatement)、SimpleExecutor(普通的執行器,預設)。

介紹完SqlSession和Executor,接下來通過傳統方式來分析SQL執行流程。首先,看到以下程式碼,便是觸發SQL執行的語句。

SqlSession sqlSession = factory.openSession();
List<User> list = sqlSession.selectList("com.silver.mapper.UserMapper.getUserByName");

1.2.1 獲取SqlSession

我們通過factory工廠物件的openSession()方法獲取到SqlSession,開啟openSession()方法,程式碼如下:

    // 6. 進入openSession方法
    @Override
    public SqlSession openSession() {
        //getDefaultExecutorType()傳遞的是SimpleExecutor
        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();
        }
    }

1.2.2 執行SqlSession介面

獲取到SqlSession過後,接下來就是執行SqlSession中的介面,也就是傳統方式程式碼的第二行,引數為全限定類名+方法名,接收到引數後,會從Configuration物件的Mapped Statement容器中取到對應的MappedStatement物件,然後交由Executor來執行。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 {
            // 根據傳入的全限定類名+方法名從對映的Map中獲得 MappedStatement 物件
            MappedStatement ms = configuration.getMappedStatement(statement);
            // 呼叫Executor中的方法來處理
            // RowBounds用來處理分頁邏輯
            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();
        }
    }

1.2.3 執行Executor介面

繼續上面的原始碼,進入executor.query()方法。

    //此方法在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);
    }
    
    @SuppressWarnings("unchecked")
    @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;
    }
    
    // 從資料庫中讀取操作
    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;
    }
    
    // SimpleExecutor中實現父類的doQuery()抽象方法
    @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 物件
    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;
    }

Executor.query()方法幾經轉折,最後會建立一個StatementHandler物件,然後將必要的引數傳遞給StatementHandler,使用StatementHandler來完成對資料庫的查詢,最終返回List結果集。從以上程式碼可以看出,Executor的功能和作用是:

  1. 根據傳遞的引數,完成SQL語句的動態解析,生成BoundSql物件,供StatementHandler使用;
  2. 為查詢建立快取,以提高效能;
  3. 建立JDBC的Statement連線物件,床底給StatementHandler物件,返回List查詢結果。

1.2.4 StatementHandler介面

StatementHandler介面主要完成兩個工作:

  1. 對於JDBC的PreparedStatement型別的物件,建立的過程中,我們使用的是SQL語句字串會包含若干個?佔位符。後面介紹如何對佔位符設值,StatementHandler通過parameterize()方法對Statement進行設值;
  2. StatementHandler通過List query(Statement statement, ResultHandler resultHandler)方法來完成執行Statement和將Statement物件返回resultSet封裝成List。

進入到StatementHandler的parameterize(statement)方法的實現:

    @Override
    public void parameterize(Statement statement) throws SQLException {
        //使用ParameterHandler物件來完成對Statement的設值
        parameterHandler.setParameters((PreparedStatement) statement);
    }
    
        @SuppressWarnings("Duplicates")
    @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);
                    }
                }
            }
        }
    }

從以上程式碼可以看出,StatementHandler的parameterize(Statement) 方法呼叫了ParameterHandlerጱsetParameters(statement) 方法,ParameterHandlerጱsetParameters(Statement)根據我們輸入的引數,對statement物件的? 佔位符進行賦值。進入到StatementHandler 的List query(Statement statement, ResultHandler resultHandler)方法的實現:

    @Override
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        PreparedStatement ps = (PreparedStatement) statement;
        // 1、執行查詢
        ps.execute();
        // 2、交由ResultHandler處理返回結果
        return resultSetHandler.handleResultSets(ps);
    }

1.3 封裝返回結果集

從query()方法我們可以看出,StatementHandler的List query(Statement statement, ResultHandler resultHandler)方法的實現,是呼叫了ResultSetHandler的handleResultSets(Statement) 方法。ResultSetHandler的handleResultSets(Statement) 方法會將Statement語句執行後得到的resultSet結果集轉換成List結果集。

    // 處理 {@link java.sql.ResultSet} 結果集
    @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` 只在儲存過程中使用,本系列暫時不考慮,忽略即可
        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++;
            }
        }

        // 如果是 multipleResults 單元素,則取首元素返回
        return collapseSingleResultList(multipleResults);
    }

2 mapper代理方式

回顧一下Mapper代理方式的寫法:

    public static void main(String[] args) {
        // 前三步相同
        InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = factory.openSession();

        // 這裡不再呼叫SqlSession的API,而是獲取了介面物件,呼叫介面中的方法
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> list = mapper.getUserByName("tom");
    }

思考一個問題,通常的Mapper介面我們都沒有寫實現類卻可以正常使用,這是為什麼呢?答案就是:動態代理

開始之前介紹一下MyBatis初始化時對介面的處理:MapperRegistry是Configuration中的一個屬性,它內部維護一個HashMap用於存放mapper介面的工廠類,每個介面對應一個工廠類。mappers中可以配置介面的包路徑,或者某個具體的介面類。

<mappers>
 <mapper class="com.silver.mapper.UserMapper"/>
 <package name="com.silver.mapper"/>
</mappers>

當解析mappers標籤時,它會判斷如果解析到的是mapper配置檔案時,會再將對應配置檔案中的增刪改查標籤一一封裝成MappedStatement物件,存入mappedStatements中。當解析到的是介面時,會建此介面對應的MapperProxyFactory物件,存入HashMap中,key為介面的位元組碼物件,value為此介面對應的MapperProxyFactory物件。

2.1 mapper動態代理

進入sqlSession.getMapper(UserMapper.class)中:

    @Override
    public <T> T getMapper(Class<T> type) {
        return configuration.getMapper(type, this);
    }
    
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return mapperRegistry.getMapper(type, sqlSession);
    }
    
    @SuppressWarnings("unchecked")
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        // 獲得 MapperProxyFactory 物件
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
        // 不存在,則丟擲 BindingException 異常
        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);
        }
    }
    
    //MapperProxyFactory類中的newInstance方法
    public T newInstance(SqlSession sqlSession) {
        // 建立了JDK動態代理的invocationHandler介面的實現類mapperProxy
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
        // 呼叫了過載方法
        return newInstance(mapperProxy);
    }
    
    @SuppressWarnings("unchecked")
    protected T newInstance(MapperProxy<T> mapperProxy) {

        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
    }

2.2 invoke()方法

在動態代理返回了例項後,我們就可以直接呼叫mapper類中的方法了,但代理物件呼叫方法,真正執行的是在MapperProxy中的invoke方法中:

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            // 如果是 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);
        }
        // 獲得 MapperMethod 物件
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        // 重點在這:MapperMethod最終呼叫了執行的方法
        return mapperMethod.execute(sqlSession, args);
    }
    
    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        //判斷mapper中的方法型別,最終呼叫的還是SqlSession中的方法
        switch (command.getType()) {
            case INSERT: {
                // 轉換引數
                Object param = method.convertArgsToSqlCommandParam(args);
                // 執行 INSERT 操作
                // 轉換 rowCount
                result = rowCountResult(sqlSession.insert(command.getName(), param));
                break;
            }
            case UPDATE: {
                // 轉換引數
                Object param = method.convertArgsToSqlCommandParam(args);
                // 轉換 rowCount
                result = rowCountResult(sqlSession.update(command.getName(), param));
                break;
            }
            case DELETE: {
                // 轉換引數
                Object param = method.convertArgsToSqlCommandParam(args);
                // 轉換 rowCount
                result = rowCountResult(sqlSession.delete(command.getName(), param));
                break;
            }
            case SELECT:
                // 無返回,並且有 ResultHandler 方法引數,則將查詢的結果,提交給 ResultHandler 進行處理
                if (method.returnsVoid() && method.hasResultHandler()) {
                    executeWithResultHandler(sqlSession, args);
                    result = null;
                // 執行查詢,返回列表
                } else if (method.returnsMany()) {
                    result = executeForMany(sqlSession, args);
                // 執行查詢,返回 Map
                } else if (method.returnsMap()) {
                    result = executeForMap(sqlSession, args);
                // 執行查詢,返回 Cursor
                } 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 = 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;
    }

相關文章