MyBatis源码解读(3)——执行器

mybatis在执行期间,主要有四大核心接口对象:

  • 执行器Executor,执行器负责整个SQL执行过程的总体控制。
  • 参数处理器ParameterHandler,参数处理器负责PreparedStatement入参的具体设置。
  • 语句处理器StatementHandler,语句处理器负责和JDBC层具体交互,包括prepare语句,执行语句,以及调用ParameterHandler.parameterize()设置参数。
  • 结果集处理器ResultSetHandler,结果处理器负责将JDBC查询结果映射到java对象。

执行器Executor

  什么是执行器?所有我们在应用层通过sqlSession执行的各类selectXXX和增删改操作在做了动态sql和参数相关的封装处理后,都被委托给具体的执行器去执行,包括一、二级缓存的管理,事务的具体管理,Statement和具体JDBC层面优化的实现等等。所以执行器比较像是sqlSession下的各个策略工厂实现,用户通过配置决定使用哪个策略工厂。只不过执行器在一个mybatis配置下只有一个,这可能无法适应于所有的情况,尤其是哪些微服务做得不是特别好的中小型公司,因为这些系统通常混搭了OLTP和ETL功能。

MyBatis源码解读(3)——执行器_第1张图片

mybatis提供了下列类型的执行器:

MyBatis源码解读(3)——执行器_第2张图片

从上述可以看出,mybatis提供了两种类型的执行器,缓存执行器与非缓存执行器(使用哪个执行器是通过配置文件中settings下的属性defaultExecutorType控制的,默认是SIMPLE),是否使用缓存执行器则是通过执行cacheEnabled控制的,默认是true。
  缓存执行器不是真正功能上独立的执行器,而是非缓存执行器的装饰器模式。
  我们先来看非缓存执行器。非缓存执行器又分为三种,这三种类型的执行器都基于基础执行器BaseExecutor,基础执行器完成了大部分的公共功能,如下所示:

MyBatis源码解读(3)——执行器_第3张图片

我们先来看下BaseExecutor的属性,从上述BaseExecutor的定义可以看出:

  1. 执行器在特定的事务上下文下执行;
  2. 具有本地缓存和本地出参缓存(任何时候,只要事务提交或者回滚或者执行update或者查询时设定了刷新缓存,都会清空本地缓存和本地出参缓存);
  3. 具有延迟加载任务;

  BaseExecutor实现了大部分通用功能本地缓存管理、事务提交、回滚、超时设置、延迟加载等,但是将下列4个方法留给了具体的子类实现:

MyBatis源码解读(3)——执行器_第4张图片

从功能上来说,这三种执行器的差别在于:

  • ExecutorType.SIMPLE:这个执行器类型不做特殊的事情。它为每个语句的每次执行创建一个新的预处理语句。
  • ExecutorType.REUSE:这个执行器类型会复用预处理语句。
  • ExecutorType.BATCH:这个执行器会批量执行所有更新语句,也就是jdbc addBatch API的facade模式。
      所以这三种类型的执行器可以说时应用于不同的负载场景下,除了SIMPLE类型外,另外两种要求对系统有较好的架构设计,当然也提供了更多的回报。

 

SIMPLE执行器

       简单执行器的实现非常的简单,我们就不展开详述了。

REUSE执行器

       从实现可以看出,REUSE和SIMPLE在doUpdate/doQuery上有个差别,不再是每执行一个语句就close掉了,而是尽可能的根据SQL文本进行缓存并重用,但是由于数据库服务器端通常对每个连接以及全局的语句(oracle称为游标)handler的数量有限制,oracle中是open_cursors参数控制,mysql中是mysql_stmt_close参数控制,这就会导致如果sql都是靠if各种拼接出来,日积月累可能会导致数据库资源耗尽。其是否有足够价值,视创建Statement语句消耗的资源占整体资源的比例、以及一共有多少完全不同的Statement数量而定,一般来说,纯粹的OLTP且非自动生成的sqlmap,它会比SIMPLE执行器更好。

 BATCH执行器

        批量执行器是JDBC Statement.addBatch的实现,对于批量insert而言比如导入大量数据的ETL,驱动器如果支持的话,能够大幅度的提高DML语句的性能(首先最重要的是,网络交互就大幅度减少了),比如对于mysql而言,在5.1.13以上版本的驱动,在连接字符串上rewriteBatchedStatements参数也就是jdbc:mysql://192.168.1.100:3306/test?rewriteBatchedStatements=true后,性能可以提高几十倍,

参见 https://www.cnblogs.com/kxdblog/p/4056010.html 以及 http://blog.sina.com.cn/s/blog_68b4c68f01013yog.html 。

因为BatchExecutor对于每个statementList中的语句,都执行executeBatch()方法,因此最差的极端情况是交叉执行不同的DML SQL语句,这种情况退化为原始的方式。比如下列形式就是最差的情况:

for(int i=0;i<100;i++) {
        session.update("insertUser", userReq);
        session.update("insertUserProfile", userReq);
    }

缓存执行器CachingExecutor

        缓存执行器相对于其他执行器的差别在于,首先是在query()方法中判断是否使用二级缓存(也就是mapper级别的缓存)。虽然mybatis默认启用了CachingExecutor,但是如果在mapper层面没有明确设置二级缓存的话,就退化为SimpleExecutor了。二级缓存的维护由TransactionalCache(事务化缓存)负责,当在TransactionalCacheManager(事务化缓存管理器)中调用putObject和removeObject方法的时候并不是马上就把对象存放到缓存或者从缓存中删除,而是先把这个对象放到entriesToAddOnCommit和entriesToRemoveOnCommit这两个HashMap之中的一个里,然后当执行commit/rollback方法时再真正地把对象存放到缓存或者从缓存中删除,具体可以参见TransactionalCache.commit/rollback方法。
  还有一个差别是使用了TransactionalCacheManager管理事务,其他逻辑就一样了。

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(MyBatis源码解读(3)——执行器)