MyBatis入坑之执行过程

这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战

MyBatis执行过程.png

01.解析配置文件得到Configuration

  • 解析配置文件,每个sql语句标签对应一个MappedStatement
  • 每个namespace对应一个MappProxyFactory,存入MapperRestry中的knownMappers,key值为Mapper接口类型,value值为MapperProxyFactory。

02.创建SqlSessionFactory

  • 根据传入的Configuration获得defaultSessionFactory

03.创建SqlSession

执行sqlSessionFactory.openSession()得到sqlSession

  • 创建事务
  • 根据策略创建Executor(simpleExecutor,ReuseExecutor,BatchExecutor),如果二级缓存开启,则用CacheingExecutor装饰上述的Executor;然后添加Executor插件。
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
复制代码
  • 创建defaultSqlSession并返回
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
复制代码

04.获取Mapper代理对象

通过sqlSession.getMapper

  • 根据传入的Mapper.class从第一步初始化knownMappers中获取对应类型的MapperProxyFactoy代理工厂对象。
  • MapperProxyFactory调用newProxyInstance获取MapperProxy代理对象生成的Mapper对象。
@SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
复制代码
  • MapperProxy中的invoke方式是调用Mapper接口方法时调用的真正方法逻辑。

05.执行代理对象Invoke方法

  • MapperProxy会把sqlseesion作为构造参数传入,执行invok时会从sqlSession中获取Executor执行。
  • 执行Executor方法,判断是否有开启二级缓存,如果有从二级缓存获取数据,如果没有数据则从Executor中获取一级缓存。一级缓存是是维护在Executor对象中的localCache,而Executor是和sqlSession一一对应,所以一级缓存是sqlSession级别的;二级缓存时namespace级别的
  • 二级缓存。二级缓存和事务绑定,事务提交后缓存才会生效,下次才会从缓存取。为什么要跟事务绑定,是为了防止回滚后数据不一致。一级缓存回滚会话会结束。
@Override
  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);
  }
复制代码
  • 一级缓存
 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 {
      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;
  }

复制代码

以下描述摘自Mybatis参考稳定
本地缓存

  • Mybatis 使用到了两种缓存:本地缓存(local cache)和二级缓存(second level cache)。

  • 每当一个新 session 被创建,MyBatis 就会创建一个与之相关联的本地缓存。任何在 session 执行过的查询结果都会被保存在本地缓存中,所以,当再次执行参数相同的相同查询时,就不需要实际查询数据库了。本地缓存将会在做出修改、事务提交或回滚,以及关闭 session 时清空。

  • 默认情况下,本地缓存数据的生命周期等同于整个 session 的周期。由于缓存会被用来解决循环引用问题和加快重复嵌套查询的速度,所以无法将其完全禁用。但是你可以通过设置 localCacheScope=STATEMENT 来只在语句执行时使用缓存。

  • 注意,如果 localCacheScope 被设置为 SESSION,对于某个对象,MyBatis 将返回在本地缓存中唯一对象的引用。对返回的对象(例如 list)做出的任何修改将会影响本地缓存的内容,进而将会影响到在本次 session 中从缓存返回的值。因此,不要对 MyBatis 所返回的对象作出更改,以防后患。

06.StatementHandler执行

  • 根据配置选择一个statmentHandler处理器(CallableStatementHandler、PreparedStatementHandler、SimpleStatementHandler)。
  • 增加StatementHandler插件
  • 执行StatementHandler对应实现方法。