深入理解 Mybatis - Executor(一)

简介: 本片博客的目的就是探究如上图中从顶级接口Executor中拓展出来的各个子执行器的功能,以及进一步了解Mybatis的一级缓存和二级缓存

承接上篇博客, 本文探究MyBatis中的Executor, 如下图: 是Executor体系图



本片博客的目的就是探究如上图中从顶级接口Executor中拓展出来的各个子执行器的功能,以及进一步了解Mybatis的一级缓存和二级缓存


预览:

  • BaseExecutor :实现了Executor的全部方法,包括对缓存,事务,连接提供了一系列的模板方法, 这些模板方法中留出来了四个抽象的方法等待子类去实现如下


protected abstract int doUpdate(MappedStatement ms, Object parameter)
 throws SQLException;
protected abstract List<BatchResult> doFlushStatements(boolean isRollback)
 throws SQLException;
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
 throws SQLException;
protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
 throws SQLException;


  • SimpleExecutor: 特点是每次执行完毕后都会将创建出来的statement关闭掉,他也是默认的执行器类型
  • ReuseExecutor: 在它在本地维护了一个容器,用来存放针对每条sql创建出来的statement,下次执行相同的sql时,会先检查容器中是否存在相同的sql,如果存在就使用现成的,不再重复获取
  • BatchExecutor: 特点是进行批量修改,她会将修改操作记录在本地,等待程序触发提交事务,或者是触发下一次查询时,批量执行修改


创建执行器#


当我们通过SqlSessionFactory创建一个SqlSession时,执行openSessionFromDataBase()方法时,会通过newExecutor()创建执行器:


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


通过这个函数,可以找到上面列举出来的所有的 执行器, MyBatis默认创建的执行器的类型的是SimpleExecutor,而且MyBatis默认开启着对mapper的缓存(这其实就是Mybatis的二级缓存,但是,不论是注解版,还是xml版,都需要添加额外的配置才能使添加这个额外配置的mapper享受二级缓存,二级缓存被这个CachingExecutor维护着)


BaseExecutor 的模板方法#


在BaseExecutor的模本方法之前,其实省略了很多步骤,我们上一篇博文中有详细的叙述,感兴趣可以去看看,下面我就简述一下: 程序员使用获取到了mapper的代理对象,调用对象的findAll(), 另外获取到的sqlSession的实现也是默认的实现DefaultSqlSession,这个sqlSession通过Executor尝试去执行方法,哪个Executor呢? 就是我们当前要说的CachingExecutor,调用它的query(),这个方法是个模板方法,因为CachingExecutor只知道在什么时间改做什么,但是具体怎么做,谁取做取决于它的实现类


如下是BaseExecutorquery()方法


@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) {
        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;
  }


BaseExecutor维护的一级缓存#


从上面的代码中,其实我们就跟传说中的Mybatis的一级缓存无限接近了,上面代码中的逻辑很清楚,就是先检查是否存在一级缓存,如果存在的话,就不再去创建statement查询数据库了

那问题来了,什么是这个一级缓存呢? **一级缓存就是上面代码中的localCache,如下图: **



再详细一点就看下面这张图:



嗯! 原来传说中的一级缓存叫localCache,它的封装类叫PerpetualCache 里面维护了一个String 类型的id, 和一个hashMap 取名字也很讲究,perpetual意味永不间断,事实上确实如此,一级缓存默认存在,也关不了(至少我真的不知道),但是在与Spring整合时,Spring把这个缓存给关了,这并不奇怪,因为spring 直接干掉了这个sqlSession


一级缓存什么时候被填充的值呢?填充值的操作在一个叫做queryFromDataBase()的方法里面,我截图如下:



其中的key=1814536652:3224182340:com.changwu.dao.IUserDao.findAll:0:2147483647:select * from user:mysql


其实看到这里,平时听到的为什么大家会说一级缓存是属于SqlSession的啊,诸如此类的话就是从这个看源码的过程中的出来的结果,如果你觉的印象不深刻,我就接着补刀,每次和数据库打交道都的先创建sqlSession,创建sqlSession的方法会在创建出DefaultSqlSession之前,先为它创建一个Executor,而我们说的一级缓存就是这个Executor的属性


何时清空一级缓存#


清空一级缓存的方法就是BaseExecutorupdate()方法


@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.");
    }
    // 清空本地缓存
    clearLocalCache();
    // 调用子类执行器逻辑
    return doUpdate(ms, parameter);
  }


SimpleExecutor#


SimpleExecutor是MyBatis提供的默认的执行器,他里面封装了MyBatis对JDBC的操作,但是虽然他叫XXXExecutor,但是真正去CRUD的还真不是SimpleExecutor,先看一下它是如何重写BaseExecutordoQuery()方法的

详细的过程在这篇博文中我就不往外贴代码了,因为我在上一篇博文中有这块源码的详细追踪


@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();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }


创建StatementHandler#


public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }


虽然表面上看上面的代码,感觉它只会创建一个叫RoutingStatementHandler的handler,但是其实上这里面有个秘密,根据MappedStatement 的不同,实际上他会创建三种不同类型的处理器,如下:


public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    switch (ms.getStatementType()) {
      case STATEMENT:
        // 早期的普通查询,极其容易被sql注入,不安全
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
       //  处理预编译类型的sql语句
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
       // 处理存储过程语句
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }


创建PreParedStatement#


点击进入上篇博文,查看如何创建PreparedStatement


执行查询#


点击进入上篇博文,里面有记录如何执行查询


关闭连接#


关于SimpleExecutor如何关闭statement,在上面一开始介绍SimpleExecutor时,我其实就贴出来了,下面再这个叫做closeStatement()的函数详情贴出来


protected void closeStatement(Statement statement) {
    if (statement != null) {
      try {
        statement.close();
      } catch (SQLException e) {
        // ignore
      }
    }
  }


相关文章
|
6月前
|
SQL 缓存 Java
MyBatis 四大核心组件之 Executor 源码解析
MyBatis 四大核心组件之 Executor 源码解析
|
SQL 存储 缓存
MyBatis四大组件Executor、StatementHandler、ParameterHandler、ResultSetHandler 详解(2)
MyBatis四大组件Executor、StatementHandler、ParameterHandler、ResultSetHandler 详解
239 0
|
SQL 存储 缓存
MyBatis四大组件Executor、StatementHandler、ParameterHandler、ResultSetHandler 详解(1)
MyBatis四大组件Executor、StatementHandler、ParameterHandler、ResultSetHandler 详解
224 0
|
缓存 安全 Java
MyBatis源码-解读Executor的三个实现类之BatchExecutor(批处理执行器)
MyBatis源码-解读Executor的三个实现类之BatchExecutor(批处理执行器)
312 0
|
SQL 缓存 Java
MyBatis源码-解读Executor的三个实现类之ReuseExecutor(重用执行器)
MyBatis源码-解读Executor的三个实现类之ReuseExecutor(重用执行器)
153 0
|
SQL Java 数据库连接
MyBatis源码-解读Executor的三个实现类之SimpleExecutor(简单执行器)
MyBatis源码-解读Executor的三个实现类之SimpleExecutor(简单执行器)
136 0
|
SQL 缓存 安全
MyBatis源码-深入理解MyBatis Executor的设计思想
MyBatis源码-深入理解MyBatis Executor的设计思想
109 0
|
SQL 缓存 Java
Java 最常见的面试题:mybatis 有哪些执行器(Executor)?
Java 最常见的面试题:mybatis 有哪些执行器(Executor)?
112 0
|
SQL 存储 XML
详解MyBatis中Executor执行SQL语句的过程
在详解MyBatis的SqlSession获取流程文章中已经知道,MyBatis中获取SqlSession时会创建执行器Executor并存放在SqlSession中,通过SqlSession可以获取映射接口的动态代理对象,动态代理对象的生成可以参考详解MyBatis加载映射文件和动态代理,可以用下图进行概括。
273 0
|
SQL 存储 缓存
深入理解 Mybatis - Executor(二)
深入理解 Mybatis - Executor(二)
161 0