Springboot中mybatis執行邏輯原始碼分析

在上一篇springboot整合mybatis原始碼分析已經講了我們的Mapper介面,userMapper是通過MapperProxy實現的一個動態代理,所有呼叫userMapper的方法,最終都會代理到MapperProxy的invoke方法上,我們這次就來看看mybatis具體的執行流程。為了簡單易懂,本次的示例用的是最簡單的查詢語句且不包含事務。

本篇文件的原始碼路徑https://github.com/wbo112/blogdemo/tree/main/springbootdemo/springboot-mybatis


  1. 我們在業務程式碼中呼叫userMapper.findAll()會呼叫到MapperProxy的invoke方法,我就從這裡開始吧

      //MapperProxy類的方法
    
    	//proxy就是我們userMapper生成的代理類,method當前是findAll(),args當前是個map "{id=1, table=user}"
    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 {
    //當前的方法findAll不是Object的,所以會走到這裡
    return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
    }
    } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
    }
    }

       //MapperProxy類的方法
    private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
    //這裡會將方法進行快取。當method不存在於methodCache中時,建立一個MapperMethodInvoker,新增到methodCache中的
    //methodCache你在MapperProxyFactory.newInstance方法的時候,從MapperProxyFactory類中傳遞過來的
    //而MapperProxyFactory這個類是在新增mapper類的時候,MapperRegistry.addMapper方法中構造出來的, knownMappers.put(type, new MapperProxyFactory<>(type));所以我們這裡的methodCache是每個mapper類都會有一個
    return MapUtil.computeIfAbsent(methodCache, method, m -> {
    //如果方法的修飾符是public,沒有abstract,static修飾的話就會走這裡,由於我們的mapper是介面,我們的方法也是abstract的,所以不會走到這個分支
    if (m.isDefault()) {
    try {
    if (privateLookupInMethod == null) {
    return new DefaultMethodInvoker(getMethodHandleJava8(method));
    } else {
    return new DefaultMethodInvoker(getMethodHandleJava9(method));
    }
    } catch (IllegalAccessException | InstantiationException | InvocationTargetException
    | NoSuchMethodException e) {
    throw new RuntimeException(e);
    }
    } else {
    //所以我們這裡最終會建立一個PlainMethodInvoker,新增到methodCache中,我們先看看MapperMethod的構造方法
    return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
    }
    });
    } catch (RuntimeException re) {
    Throwable cause = re.getCause();
    throw cause == null ? re : cause;
    }
    }


    //MapperMethod類的方法
    public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    //command主要是用mapperInterface和method在config中查詢對應sql的定義,返回sql id和 sqlCommandType
    this.command = new SqlCommand(config, mapperInterface, method);
    //method主要是通過解析我們的method,得到方法的返回型別,引數型別,分頁等等相關資訊
    this.method = new MethodSignature(config, mapperInterface, method);
    }

    構造完 new PlainMethodInvoker之後就會調到它的invoke方法去處理,繼續呼叫到上面MapperMethod的execute方法,我們進去看看

      //所有的增刪改查都是在這裡走不同的分支來處理的,我們當前的是查詢,我們看看select分支
    public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    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()) {
    executeWithResultHandler(sqlSession, args);
    result = null;
    //我們這裡的findAll()方法返回值是個List,所以會走到method.returnsMany()這個分支,我們進去看看
    } else if (method.returnsMany()) {
    result = executeForMany(sqlSession, args);
    } else if (method.returnsMap()) {
    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 = Optional.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;
    }

    	//MapperMethod的方法
    private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    //這裡會對引數做個處理,如果mapper方法有多個引數的話,會封裝到一個map裡面,我們這裡只有一個引數,已經是map了,所以我們這裡只是從Object[]返回args[0]
    Object param = method.convertArgsToSqlCommandParam(args);
    //通過mapper方法上有沒有 RowBounds.class引數來判斷是否有分頁,我們這裡沒有
    if (method.hasRowBounds()) {
    RowBounds rowBounds = method.extractRowBounds(args);
    result = sqlSession.selectList(command.getName(), param, rowBounds);
    } else {
    //所以我們這裡會走到這個分支中
    result = sqlSession.selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
    if (method.getReturnType().isArray()) {
    return convertToArray(result);
    } else {
    return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
    }
    }
    return result;
    }

    會呼叫到SqlSessionTemplate的selectList方法,繼續呼叫到sqlSessionProxy的selectList方法,這個sqlSessionProxy也是個動態代理,是在SqlSessionTemplate構造方法中初始化的

      public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
    PersistenceExceptionTranslator exceptionTranslator) { notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required"); this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
    new Class[] { SqlSession.class }, new SqlSessionInterceptor());
    }

    呼叫sqlSessionProxy的方法,最終都會呼叫到SqlSessionInterceptor(這是個內部類,在SqlSessionTemplate類中)的invoke方法

        @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //這裡會返回一個真正執行sql的SqlSession,我們進 getSqlSession方法去看看
    SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
    SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
    ......
    }

     //SqlSessionUtils的方法
    
      public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
    PersistenceExceptionTranslator exceptionTranslator) { notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
    //如果開啟了事務,在同一個事務中,第一次返回的holder是空的,後面返回的holder都不為空,直接holder中返回SqlSession
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
    //如果holder不為空,根據executorType相同,返回之前快取的SqlSession
    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
    return session;
    } LOGGER.debug(() -> "Creating a new SqlSession");
    //開啟了事務,在同一個事務中,第一次執行,或者沒有開啟事務,就會走到這裡
    session = sessionFactory.openSession(executorType);
    //如果開啟了事務,就會將創建出來的session進行快取
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session); return session;
    }

    我們看看session = sessionFactory.openSession(executorType)這句,最終會呼叫到DefaultSqlSessionFactory.openSessionFromDataSource中

      private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
    final Environment environment = configuration.getEnvironment();
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    //這裡會建立一個SpringManagedTransaction
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    //在這裡根據execType建立Executor,當前的execType是一個SIMPLE,會建立SimpleExecutor, 如果開啟了快取,就會再建立CachingExecutor,包裝SimpleExecutor
    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();
    }
    }

    我們再回頭看SqlSessionInterceptor.invoke後面的執行,建立完了SqlSession,就會去呼叫Object result = method.invoke(sqlSession, args),我們去看看這裡的執行,這句是通過反射來執行的,這裡的sqlSession是上面建立的DefaultSqlSession類來完成的

    我們來看看DefaultSqlSession的方法,所有的增刪改查方法都有具體的實現,我們最終mapper的方法都是通過這個類來實現的。

    我們看看本次呼叫的selectList方法

      //最終會呼叫到這個
    private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
    //根據sql id獲取到 MappedStatement(這個包含了我們sql語句的定義的詳細資訊)
    MappedStatement ms = configuration.getMappedStatement(statement);
    //這裡會繼續去呼叫CachingExecutor的query方法
    return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
    } finally {
    ErrorContext.instance().reset();
    }
    }

    //CachingExecutor的方法
    
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //這裡主要是會對我們的sql語句中"${"、"}"和"#{"、"}" 及之間的內容進行替換。
    //將"${"、"}"根據裡面的內容直接進行字串替換
    //將"#{"、"}"替換成?,根據引數順序將引數封裝到parameterMappings中
    //我們的sql語句是" SELECT * FROM ${table} where id =#{id}",我們的入參map中有table=user,這裡就會進行替換,替換之後的sql就是 "SELECT * FROM user where id =?"
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    //這裡會生成一個查詢快取的key
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    //繼續走到這裡去看看
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }


    //CachingExecutor的方法
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
    //會在這裡獲取快取,這裡的快取也就是我們常說的二級快取了,由於我們的mapper.xml檔案中配置了<cache/>,所以這裡的cache也就不會為空,這個cache在<mapper>標籤下面,所以每個mapper的cache都是各自來控制的。
    //這裡的快取最終是LruCache,看名字就知道這是一種LRU(Least recently used,最近最少使用)演算法根據資料的歷史訪問記錄來進行淘汰資料,
    //Cache有多種實現,具體要那種,我們是可以指定的
    //在其他的update,delete等等方法會呼叫 flushCacheIfRequired(ms);將快取清空
    Cache cache = ms.getCache();
    if (cache != null) {
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
    ensureNoOutParams(ms, boundSql);
    @SuppressWarnings("unchecked")
    //在這裡首先會在快取中查詢,如果快取有,就從快取中獲取,直接返回
    List<E> list = (List<E>) tcm.getObject(cache, key);
    if (list == null) {
    //繼續會呼叫到這裡,這裡的delegate是SimpleExecutor ,這個方法在它的父類,BaseExecutor中
    list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    //在這裡會將查村出來的結果快取到TransactionalCache.entriesToAddOnCommit中,
    //注意:這裡並沒有快取到cache裡面
    tcm.putObject(cache, key, list); // issue #578 and #116
    }
    return list;
    }
    } return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

      //BaseExecutor的方法
    @SuppressWarnings("unchecked")
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ......
    try {
    queryStack++;
    //在這裡會從 localCache中查詢,這個其實就我們說的一級快取真正存放的位置。這個localCache是當前類的一個屬性,而在沒有開啟事務的時候,我們每次都會新建立一個SimpleExecutor,所以這個localCache也就都是空的
    //如果開啟事務,在同一個事務中,第一次請求會建立 SimpleExecutor,之後都是重用同一個SimpleExecutor
    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();
    //這裡的LocalCacheScope預設是LocalCacheScope.SESSION,如果是LocalCacheScope.STATEMENT的話就會清空快取
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
    // issue #482
    clearLocalCache();
    }
    }
    return list;
    }

    	//BaseExecutor的方法
    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 {
    //在這裡會獲取connection,執行資料庫的查詢,並返回我們需要的型別
    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;
    }

    我們現在退回到SqlSessionTemplate.invoke方法看獲取到結果後的處理

      //SqlSessionTemplate的方法
    private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
    SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
    try {
    //獲取到執行結果,在這裡返回
    Object result = method.invoke(sqlSession, args);
    //由於我們本次不涉及事務,所以會從這個分支返回
    if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
    // force commit even on non-dirty sessions because some databases require
    // a commit/rollback before calling close()
    //在這裡會將我們之前快取到TransactionalCache.entriesToAddOnCommit中的返回結果,儲存到MappedStatement.cache中
    //由於在新增到cache中會呼叫,serialize((Serializable) object),通過序列化返回byte[]陣列,所以如果我們xml檔案中開啟了快取,那我們返回結果包含的類就需要實現Serializable介面
    sqlSession.commit(true);
    }
    return result;
    } catch (Throwable t) {
    ......
    } finally {
    if (sqlSession != null) {
    closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
    }
    }
    }
    }

    上面就是整個springboot中mybatis呼叫查詢的整個流程了 。