• Mybatis源码分析-BaseExecutor


    根据前文Mybatis源码分析-SqlSessionTemplate的简单分析,对于SqlSession的CURD操作都需要经过Executor接口的update/query方法,本文将分析下BaseExecutor如何解析执行sql语句

    BaseExecutor-抽象类

    其是Executor接口的实现类但为抽象类,另外一个则为具体实现类为CachingExecutor,主要是通过装饰器的设计模式在原来的executor上再附上缓存的属性,有兴趣的可自行查阅。先从构造函数看一发

      protected BaseExecutor(Configuration configuration, Transaction transaction) {
        //事务对象
        this.transaction = transaction;
        this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
        this.localCache = new PerpetualCache("LocalCache");
        this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
        //表明exector的状态
        this.closed = false;
        //主文件属性,主要获取MappedStatement对象
        this.configuration = configuration;
        this.wrapper = this;
      }
    

    BaseExecutor#update()-SqlSession之insert/update/delete入口

    具体源码如下

      @Override
      public int update(MappedStatement ms, Object parameter) throws SQLException {
        ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
        if (closed) {
          throw new ExecutorException("Executor was closed.");
        }
        // 清除本地缓存,基于SqlSession范围作用
        clearLocalCache();
        //供子类复写执行CUD操作
        return doUpdate(ms, parameter);
      }
    

    BaseExecutor#query()-SqlSession之select入口

    具体源码如下

      @Override
      public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        //获取绑定的sql,并将参数对象与sql语句的#{}一一对应
        BoundSql boundSql = ms.getBoundSql(parameter);
        //获取cacheKey供缓存,包含完整的语句、参数等,确保CacheKey的唯一性
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        //比原先多传入CacheKey和BoundSql参数
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
     }
    

    具体的查询处理逻辑如下

      @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());
        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) {
            //此处尝试对Callable类型的表达式进行处理,主要是针对mode=out类型的参数
            //此参数主要是通过map来定义,直接从map中获取
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
          } else {
            //从数据库中获取并进行缓存处理,其也会调用子类需复写的doQuery()方法
            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;
      }
    

    由上可知,BaseExecutor对CRUD操作均转化为对子类的doUpdate()/doQuery()方法的调用,并一般都会相应的结果进行缓存以免频繁请求数据库导致性能下降(称之为一级缓存)。本文则从SimpleExecutor子类来进行分析

    SimpleExecutor

    分别看下SimpleExecutor复写的doUpdate()和doQuery()方法,具体源码如下
    doUpdate()

      @Override
      public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
        Configuration configuration = ms.getConfiguration();
        //创建StatementHandler来处理update()
        StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
        //创建表达式对象Statement
        Statement stmt = prepareStatement(handler, ms.getStatementLog());
        return handler.update(stmt);
      }
    

    doQuery()

      @Override
      public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Configuration configuration = ms.getConfiguration();
        //创建StatementHandler来处理query()
        StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        //创建表达式对象Statement
        Statement stmt = prepareStatement(handler, ms.getStatementLog());
        return handler.<E>query(stmt, resultHandler);
      }
    

    doUpdate()/doQuery()代码的执行逻辑一致,均是先创建StatementHandler对象,然后通过prepareStatement()方法创建表达式对象,供前者调用处理update/query方法

    SimpleExecutor#prepareStatement()-创建预表达式对象

    逻辑如下

      //handler对象对应的为RoutingStatementHandler对象,其实也是个适配管理类
      //可根据MappedStatement的statementType来确定表达式处理handler类,后续讲解
      private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        //获取连接对象,如果该日志等级为debug,则会打印相应的处理日志,采用代理实现
        Connection connection = getConnection(statementLog);
        //创建真实的Statement对象,比如SimpleStatement/PreparedStatement/CallableStatement
        stmt = handler.prepare(connection, transaction.getTimeout());
        //请求参数,常用在preparedStatement用来设置相应的请求参数
        handler.parameterize(stmt);
        return stmt;
      }
    

    关于缓存

    这里的org.apache.ibatis.executor.BaseExecutor以及org.apache.ibatis.executor.CachingExecutor在执行相应的SQL语句查询前都会进行一次相应的缓存处理。前者称之为一级缓存,后者称之为二级缓存

    什么意思呢???


    一级缓存

    首先有必要先解释下一级缓存,从上文的代码中可以发现其就是针对相同的SQL查询,会优先从本地缓存查询,如果没有再从数据库查询;如果对应的SqlSession一旦有CUD操作,则SqlSession内的本地缓存将被重新清除,下一次的R操作则必须从数据库中读取了~~~~

    由此可以得知,mybatis的一级缓存是基于SqlSession的,不同的SqlSession针对相同的SQL操作有可能得到的返回结果是不一样。且一旦有更新操作,则一级缓存缓存的所有结果集都被清空~~~


    二级缓存

    再而对二级缓存作下解释,其是由CachingExecutor在执行query()方法的时候会再加一个判断,笔者把代码贴出来好针对性的理解

      @Override
      public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
          throws SQLException {
        // 针对MappedStatement级别获取Cache对象
        Cache cache = ms.getCache();
        if (cache != null) {
          flushCacheIfRequired(ms);
          // query操作一般此判断均通过 ResultHandler一般DAO接口不会定义此入参
          if (ms.isUseCache() && resultHandler == null) {
            ensureNoOutParams(ms, boundSql);
            @SuppressWarnings("unchecked")
            // 从mappedStatement的cache对象中获取对应的结果集
            List<E> list = (List<E>) tcm.getObject(cache, key);
            if (list == null) {
              // 为空则还是尝试通过一级缓存去获取
              list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
              // 放入对应的cache中(当Sqlsession执行commit()操作时则塞入)
              tcm.putObject(cache, key, list); // issue #578 and #116
            }
            return list;
          }
        }
        return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
      }
    

    可以发现,二级缓存是基于namespace作用域来的,也就是MappedStatement级别。一旦有对应的MappedStatement对象执行了CUD操作则会清空MappedStatement级别对应的Cache,而不影响其它MappedStatement的对象。

    举例子,也就是说针对用户表以及用户具体信息两张表的MappedStatement,在开启二级缓存的时候就是独立而互相不影响的~~~这或许会造成不一致的现象


    写下简短的结论

    1.一级缓存作用于SqlSession,默认是开启的

    2.二级缓存作用于MappedStatement,而其则需要在对应的mapper文件下添加<cache />标签才可生效,并且每个CURD标签都可以使用useCache属性来决定是否使用二级缓存(默认是TRUE~)

    3.二级缓存作用于MappedStatement,所以其不能对细粒度高的场景不适合,也就是前文提及的可能不一致的场景也就是二级缓存默认是不开启的如果更新操作少而查询操作多的场景则可以考虑采取二级缓存提升性能哦~~~

    小结

    1. BaseExecutor抽象类提供了对CRUD操作的入口,并带有缓存效应,子类只需要复写doUpdate()和doQuery()抽象方法即可

    2. 在生成Statement对象来执行SQL时对应的池的不同反应
      SimpleExecutor-简单的处理实现类,即基本每次对相同的sql语句都会创建新的Statement对象;ReuseExecutor-复用处理实现类,即对相同的sql语句会缓存Statement对象;BatchExecutor-批处理实现类

    3. 最终获取Statement对象以及执行sql语句的解释权在于StatementHandler接口,详情看下节内容

  • 相关阅读:
    多级部署下的SuperMap iServer 2.0 JS 聚合功能(一)
    Kubernetes&Docker集群部署
    股票数据存储系统(KeyValue存储)设计与实现
    Ajax+Tornado模拟长、短轮询
    REST架构网站改写:前端MVC Angular.js,Web框架 Express.js, 数据库 MongoDB
    SQLite数据库C++ API封装
    一致性哈希(Consistent Hashing)算法的C++实现
    数据结构——排序
    数据结构——折半查找
    索引学习笔记
  • 原文地址:https://www.cnblogs.com/question-sky/p/7353418.html
Copyright © 2020-2023  润新知