以下文章为本人阅读源码时的随笔记录,比较混乱且不一定正确,仅供参考。

源码版本

<dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>

开始

首先找入口处,也就是基于Spring自己实现的SPI机制。这个是springboot的基础,我就不细说了,直接定位到下图位置:

springboot和mybatisplus代码生成工具 springboot与mybatis整合的过程代码_spring boot


第一个明显是啥语言方向的,果断进第二个org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration。我们知道mybatis跟数据库交互都是通过sqlsession这个类去交互的。随便写个DEMO DEBUG可知,sqlsession接口的实现类是SqlSessionTemplate,如下图。

springboot和mybatisplus代码生成工具 springboot与mybatis整合的过程代码_spring boot_02


好,那么我们就看看啥时候把这个sqlsession配置进去的即可,回到配置类,找到如下代码:

@Bean
  @ConditionalOnMissingBean
  public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    ExecutorType executorType = this.properties.getExecutorType();
    if (executorType != null) {
      return new SqlSessionTemplate(sqlSessionFactory, executorType);
    } else {
      return new SqlSessionTemplate(sqlSessionFactory);
    }
  }

继续跟进去看一下,但是现在有一个executorType 的判断,鬼知道他会进哪一个构造方法呢,直接上断点;好,是第二个

springboot和mybatisplus代码生成工具 springboot与mybatis整合的过程代码_sql_03

最后的构造函数代码如下:

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());
  }

看得出来SqlSessionTemplate这玩意主要就是持有sqlSessionFactory 、sqlSession的代理对象了。
其实在这里,最重要的就是SqlSessionInterceptor。sqlSession代理对象的作用,也全在这个拦截器身上了。

重新DEBUG跟一下一个简单的查询过程,首先是MapperProxy类的invoke方法,

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (method.isDefault()) {
        if (privateLookupInMethod == null) {
          return invokeDefaultMethodJava8(proxy, method, args);
        } else {
          return invokeDefaultMethodJava9(proxy, method, args);
        }
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

继续跟MapperMethod的execute()方法,我是selectList所以进到这里,这里不是重点,继续下去

springboot和mybatisplus代码生成工具 springboot与mybatis整合的过程代码_spring boot_04


executeForMany的具体实现,注意看走的是我注解写的地方,这里开始调用sqlsession进行查询了

private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    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;
  }

继续走,进入了sqlsession的实现类,sqlsessionTemplete

@Override
  public <E> List<E> selectList(String statement, Object parameter) {
    return this.sqlSessionProxy.selectList(statement, parameter);
  }

在往里走,终于到了代理类了,继续走,

springboot和mybatisplus代码生成工具 springboot与mybatis整合的过程代码_sql_05

最后他还是交给的DefaultSqlSession去执行的,

@Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      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();
    }
  }

然后DefaultSqlSession又交给executor,看到这,其实已经有点偏题了,但是没关系,顺便看看mybatis的一级缓存跟二级缓存把,CachingExecutor的query方法,先通过sql,入参啥的获取到一个cacheKey,后面应该就是拿这个CacheKey去缓存里面匹配。

@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

继续走,发现了一个缓存的地方,这个就是二级缓存,这个是需要手动开启的,也就是这行代码ms.isUseCache()。

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    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) {
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

继续走到了BaseExeutor的query方法,缓存代码在这,localCache.getObject(key),很明显,用这个缓存的话没有任务先前逻辑判断,所以说是默认开启且无法关闭的。

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) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      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) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

结束

好了,今天就到这里了,总结一下,我们看了mybatis跟spring集成的核心配置类,以及顺带跟了一下mybatis的执行过程,看到了一级缓存跟二级缓存的具体实现。