Mybatis成神之路-Mybatis执行体系(Executor执行器详解)

1,899 阅读17分钟

一 Mybatis执行器存在意义和简单介绍

1.1 执行器存在的意义 执行器的种类有:基础执行器、简单执行器、重用执行器和批处理执行器,此外通过装饰者设计模式额外添加了一个缓存执行器。 执行器存在的意义就是去处理SQL的共性。 如果说每个SQL调用是独立的,不需要缓存,不需要事务,也不需集中在一起进行批处理的话,Executor也就没有存在的必要。但事实上这些都是MyBatis中不可或缺的特性。所以才设计出Executor这个组件。 多个SQL可能拥有相同的共性:比如需要在同一个事务下使用,比如批处理等等

1.2 在MyBatis中执行器种类和功能的简单介绍:

1.2.1 Executor:执行器顶层接口(一般都是将某个工具的最基本的功能的抽象方法们定义成一个接口,只要继承自该类,都要实现该接口的所有抽象方法)
预定义的Executor执行器的的基本功能有:

1 update方法(所有对SQL的增删改方法都是执行update中的逻辑)
2 query方法(所有对SQL的查询,执行的都是query逻辑)
3 queryCursor(查询游标)
4 flushStatements(批处理刷新)
5 commit,rollback 提交/回滚
6 createCacheKey根据输入参数MappedStatement(即StatementID),输入参数,分页参数,SQL语句,创建一级/二级缓存的key(一级二级缓存的实质就是PerpetualCache类中的cache属性,都是基于内存存储的HashMap数据结构)

7 boolean isCached(MappedStatement ms, CacheKey key); 一级缓存是否缓存标志位 该方法的唯一实现类存在于二级缓存执行器CachingExecutor和基础缓存执行器BaseExecutor,使用快捷键ALT + F7(IEDA快捷键) 查看该方法被用到的地方如下:

8 clearLocalCache 清除一级缓存
9 deferLoad 延迟加载
10 getTransaction 事务相关
11 close 一级缓存执行器BaseExecutor中的实现内容是:清空一级缓存,二级缓存执行器CachingExecutor中的实现内容是清空二级缓存
12 isClosed 是否关闭标志位
13 setExecutorWrapper

Executor总结:

执行器功能:Executor是MyBatis执行器接口,执行器的功能包括(注意:Executor不执行SQL,真正执行SQL是在StatementHandler中执行):

基本功能 缓存维护 事物管理 辅助功能
改、查,没有增删的原因是,所有的增删操作都可以归结到改 一级缓存服务,功能包括创建缓存Key、清理缓存、判断缓存是否存在 提交、回滚、关闭、批处理刷新 提交,关闭执行器,批处理刷新(flushStatements)

1.2.2 SimpleExecutor(简单执行器)
1.2.2.1 源码介绍

doFlushStatements:刷新statement,从而提交批处理,但是此处是空方法实现,因为SimpleExecutor不支持批处理
doQuery:调用StatementHandler从数据库中查询数据
doUpdate: 所有增删改操作

1.2.2.2 Test

前置条件:

mybatis配置文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--mybatis的主配置文件-->
<configuration>

     <properties resource="app.properties">
    </properties>
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <typeAliases>
    </typeAliases>
    <!--配置环境-->
    <environments default="dev">
        <!--配置mysql的环境-->
        <environment id="dev">
            <!--配置事务的环境-->
            <transactionManager type="JDBC"/>
            <!--配置数据源(连接池)-->
            <dataSource type="POOLED">
                <!--配置连接数据库的4个基本信息-->
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <!--指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件-->
    <mappers>
        <package name="org.coderead.mybatis"/>
    </mappers>
</configuration>

Java 前置条件代码:

public class ExecutorTest {

    private Configuration configuration;
    private Connection connection;
    private JdbcTransaction jdbcTransaction;
    /**
     * SQL映射
     */
    private MappedStatement ms;
    private SqlSessionFactory factory;

    @Before
    public void init() throws SQLException {
        // 获取构建器
        SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
        // 解析XML 并构造会话工厂
        factory = factoryBuilder.build(ExecutorTest.class.getResourceAsStream("/mybatis-config.xml"));
        configuration = factory.getConfiguration();
        jdbcTransaction = new JdbcTransaction(factory.openSession().getConnection());
        // 获取SQL映射
        ms = configuration.getMappedStatement("org.coderead.mybatis.UserMapper.selectByid");
    }
}

Mapper.java:

public interface UserMapper {
    @Select({" select * from users where id=#{1}"})
    User selectByid(Integer id);
}

Test:

 /**
    * 简单执行器
    * @Param:
    * @return:
    * @Author: geekAntony
    * @Date: 2020/6/16
    */
    @Test
    public void simpleTest() throws SQLException {
        SimpleExecutor executor = new SimpleExecutor(configuration, jdbcTransaction);
        List<Object> list = executor.doQuery(ms, 10, RowBounds.DEFAULT,
                SimpleExecutor.NO_RESULT_HANDLER, ms.getBoundSql(10));

        executor.doQuery(ms, 10, RowBounds.DEFAULT,
                SimpleExecutor.NO_RESULT_HANDLER, ms.getBoundSql(10));
        System.out.println(list.get(0));
    }

doQuery方法解析:

doQuery方法
     第一个参数 MappedStatement ms 是SQl映射: ms = configuration.getMappedStatement("org.coderead.mybatis.UserMapper.selectByid");
     第二个参数 Object parameter 具体的,实际的业务参数 (id=10)
     第三个参数 RowBounds rowBounds 行的返回范围 (RowBounds.DEFAULT表示默认不分页)
     第四个参数 ResultHandler resultHandler 自定义的结果处理 (SimpleExecutor.NO_RESULT_HANDLER 表示不使用结果处理器)
     第五个参数 BoundSql boundSql 动态SQL语句 " select * from users where id=#{1}"

Console: 查看日志打印

17:31:59,509 DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource:405 - Created connection 2003147568.
17:31:59,510 DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction:100 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@77659b30]
17:31:59,534 DEBUG org.coderead.mybatis.UserMapper.selectByid:143 - ==>  Preparing: select * from users where id=? 
17:31:59,567 DEBUG org.coderead.mybatis.UserMapper.selectByid:143 - ==> Parameters: 10(Integer)
17:31:59,608 DEBUG org.coderead.mybatis.UserMapper.selectByid:143 - <==      Total: 1
17:31:59,609 DEBUG org.coderead.mybatis.UserMapper.selectByid:143 - ==>  Preparing: select * from users where id=? 
17:31:59,609 DEBUG org.coderead.mybatis.UserMapper.selectByid:143 - ==> Parameters: 10(Integer)
17:31:59,619 DEBUG org.coderead.mybatis.UserMapper.selectByid:143 - <==      Total: 1

SiimpleExecutor总结:'
1 从打印的日志上来看,SQL编译了两次,执行了两次
2 SimpleExecutor执行器默认使用的JDBC处理器是PreparedStatement(简单执行器可以对应的JDBC处理器可以是任意三种,并不一定非得对应普通JDBC处理器Statement)
3 每次SQL请求都会创建一个新的预处理器PreparedStatement
1.2.3 ReuseExecutor(可重用执行器) 1.2.3.1 源码介绍

Test1:

/**
     * 重用执行器(可重用执行器中对应的JDBC处理器statement既可以是PreparedStatement,也可以是Statement)
     * @throws SQLException
     */
    @Test
    public void ReuseTest() throws SQLException {
        ReuseExecutor executor = new ReuseExecutor(configuration, jdbcTransaction);
        List<Object> list = executor.doQuery(ms, 10, RowBounds.DEFAULT,
                SimpleExecutor.NO_RESULT_HANDLER, ms.getBoundSql(10));
        //相同的SQL会缓存对应的 PrepareStatement-->缓存周期:一个会话有效期内
        executor.doQuery(ms, 10, RowBounds.DEFAULT,
                SimpleExecutor.NO_RESULT_HANDLER, ms.getBoundSql(10));
        System.out.println(list.get(0));
    }

Console(执行两次doQuery查询,编译了一次SQL,执行了两次):

17:39:24,137 DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource:405 - Created connection 2003147568.
17:39:24,137 DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction:100 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@77659b30]
17:39:24,164 DEBUG org.coderead.mybatis.UserMapper.selectByid:143 - ==>  Preparing: select * from users where id=? 
17:39:24,202 DEBUG org.coderead.mybatis.UserMapper.selectByid:143 - ==> Parameters: 10(Integer)
17:39:24,238 DEBUG org.coderead.mybatis.UserMapper.selectByid:143 - <==      Total: 1
17:39:24,238 DEBUG org.coderead.mybatis.UserMapper.selectByid:143 - ==> Parameters: 10(Integer)
17:39:24,249 DEBUG org.coderead.mybatis.UserMapper.selectByid:143 - <==      Total: 1

Test2:

@Test
public void sessionByReuseTest() {
        SqlSession sqlSession = factory.openSession(ExecutorType.REUSE,true);
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //MappedStatement
        mapper.selectByid(10); //JDBC: statement
        mapper.selectByid3(12);
}

Console(:编译了一次SQL,执行了两次):

17:42:10,145 DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource:405 - Created connection 2003147568.
17:42:10,145 DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction:100 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@77659b30]
17:42:10,181 DEBUG org.coderead.mybatis.UserMapper:62 - Cache Hit Ratio [org.coderead.mybatis.UserMapper]: 0.0
17:42:10,187 DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction:136 - Opening JDBC Connection
17:42:10,268 DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource:405 - Created connection 530696881.
17:42:10,273 DEBUG org.coderead.mybatis.UserMapper.selectByid:143 - ==>  Preparing: select * from users where id=? 
17:42:10,308 DEBUG org.coderead.mybatis.UserMapper.selectByid:143 - ==> Parameters: 10(Integer)
17:42:10,340 DEBUG org.coderead.mybatis.UserMapper.selectByid:143 - <==      Total: 1
17:42:10,341 DEBUG org.coderead.mybatis.UserMapper:62 - Cache Hit Ratio [org.coderead.mybatis.UserMapper]: 0.0
17:42:10,341 DEBUG org.coderead.mybatis.UserMapper.selectByid:143 - ==> Parameters: 13(Integer)
17:42:10,352 DEBUG org.coderead.mybatis.UserMapper.selectByid:143 - <==      Total: 0

ReuseExecutor总结:
1 一次会话(SqlSession)发起了两次SQL请求,用的是两个不同的statementID,一个是selectList,一个是selectOne,但是经过一系列计算,SQL语句是一样的(SQL语句一样,输入的参数可以不一样),会命中ReuseExecutor中的statement缓存(所以只会预编译一次SQL)(此缓存非一级缓存)
2 ReuseExecutor执行器默认使用的JDBC处理器是PreparedStatementHandler,PreparedStatementHandler默认使用的是PreparedStatement,同样它也可以使用Statement
3 相同的SQL会缓存PreparedSatement(相同的SQL只执行一次预处理),缓存周期在一个会话(Mybatis的SqlSession内,而不是Spring的SqlSession)内有效

1.2.4 BatchExecutor(可重用执行器) 1.2.4.1 源码介绍

Test1:

 /**
     * 批处理执行器
     * @throws SQLException
     */
    @Test
    public void BatchTest() throws SQLException {
        BatchExecutor executor = new BatchExecutor(configuration, jdbcTransaction);
        MappedStatement setName = configuration
                .getMappedStatement("org.coderead.mybatis.UserMapper.setName");
        Map param = new HashMap<String,Object>();
        param.put("arg0", 10);
        param.put("arg1", "geekAntony");
        executor.doUpdate(setName, param); //设置参数并执行
        executor.doUpdate(setName, param);// 设置参数并执行
        executor.doFlushStatements(false);
    }

定义UserMapper:

public interface UserMapper {
    @Update("update  users set name=#{arg1} where id=#{arg0}")
    int setName(Integer id, String name);
}

Console(编译了一次,传递了两次参数):

18:05:07,120 DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource:405 - Created connection 2003147568.
18:05:07,120 DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction:100 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@77659b30]
18:05:07,147 DEBUG org.coderead.mybatis.UserMapper.setName:143 - ==>  Preparing: update users set name=? where id=? 
18:05:07,185 DEBUG org.coderead.mybatis.UserMapper.setName:143 - ==> Parameters: geekAntony(String), 10(Integer)
18:05:07,185 DEBUG org.coderead.mybatis.UserMapper.setName:143 - ==> Parameters: geekAntony(String), 10(Integer)

Test2

在使用BatchExecutor执行器的时候,执行以下SQL会使用多少个Statement,返回的数据集元素有多少个?

/**
     * batchResults数据集中有多少个元素
     */
    @Test
    public void sessionBatchTest1(){
        SqlSession sqlSession = factory.openSession(ExecutorType.BATCH,true);
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //MappedStatement
        // 1.sql 相同 2.MappedStatement 3.必须是连续的  =》JDBC statement
        mapper.setName(10,"道友友谊永存"); 
        mapper.setName(12,"道友友谊永存2");
        mapper.addUser(Mock.newUser());
        mapper.addUser(Mock.newUser());
        mapper.addUser(Mock.newUser());
        mapper.setName(12,"道友友谊永存2");
        List<BatchResult> batchResults = sqlSession.flushStatements();
        //batchResults数据集有多少个元素 答案:C 3个
        // A:2个 B:5个 C:3个
    }

Console:

18:10:24,473 DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource:54 - Created connection 209429254.
18:10:24,473 DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction:54 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@c7ba306]
18:10:24,526 DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction:54 - Opening JDBC Connection
18:10:24,611 DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource:54 - Created connection 2027317551.
18:10:24,616 DEBUG org.coderead.mybatis.UserMapper.setName:54 - ==>  Preparing: update users set name=? where id=? 
18:10:24,658 DEBUG org.coderead.mybatis.UserMapper.setName:54 - ==> Parameters: 道友友谊永存(String), 10(Integer)
18:10:24,659 DEBUG org.coderead.mybatis.UserMapper.setName:54 - ==> Parameters: 道友友谊永存2(String), 12(Integer)
18:10:24,660 DEBUG org.coderead.mybatis.UserMapper.addUser:54 - ==>  Preparing: INSERT INTO `users`( `name`, `age`, `sex`, `email`, `phone_number`) VALUES ( ?, ?, ?, ?, ?) 
18:10:24,661 DEBUG org.coderead.mybatis.UserMapper.addUser:54 - ==> Parameters: mock_24659(String), 19(String), 男(String), modk@coderead.cn(String), 888888(String)
18:10:24,661 DEBUG org.coderead.mybatis.UserMapper.addUser:54 - ==> Parameters: mock_24661(String), 19(String), 男(String), modk@coderead.cn(String), 888888(String)
18:10:24,662 DEBUG org.coderead.mybatis.UserMapper.addUser:54 - ==> Parameters: mock_24662(String), 19(String), 男(String), modk@coderead.cn(String), 888888(String)
18:10:24,663 DEBUG org.coderead.mybatis.UserMapper.setName:54 - ==>  Preparing: update users set name=? where id=? 
18:10:24,663 DEBUG org.coderead.mybatis.UserMapper.setName:54 - ==> Parameters: 道友友谊永存2(String), 12(Integer)

结论:
SQL编译了3次,执行了5次,BatchExecutor执行器SQL重用的条件有3条:
1 都满足同一个MapperStatement(即同一个Mapper中的SQL)
2 SQL必须相同,才能重用SQL
mapper.setName(10,"道友友谊永存");
mapper.setName(12,"道友友谊永存2");
两者SQL相同
mapper.addUser(Mock.newUser());
mapper.addUser(Mock.newUser());
mapper.addUser(Mock.newUser());
三者SQL相同
3 不连续的SQL就算满足SQL相同也不会重用Statement

BatchExecutor总结:

1 批处理执行器,只对修改有效,对于查询操作来说,相当于SimpleExecutor
2 批处理提交修改,必须执行flushStatement才会生效,sqlSession.flushStatements();
即SqlSession调用flushStatements()的时候才会一起提交
3

4 BatchExecutor设置自动提交是无效的(必须sqlSession.flushStatements();以后才会提交)

1.2.5 BaseExecutor(基础执行器)

BaseExecutor的作用: 公共执行器就是去处理三个子执行器中公有的功能逻辑,即1级缓存和事务:
1 重用:只有SQL用过以后,下一个SQL可以继续用,才能够重用
2 事务:多个SQL合并在一起就叫事务(一般使用Spring来管理事务,而不是Mybatis)
3 缓存:执行器的功能,除了增删改查以外,有缓存和事务,还有多个SQL的重用,这些都是多个SQL执行过程中有共性的地方。(两个SQL,参数和SQL语句一样,那么就可以命中缓存,那么说缓存就是这两个SQL语句的共性。)

BaseExecutor的功能图解:

基础执行器的构成主要是query方法和update方法,和几个抽象方法doQuery和doUpdate方法,查询的时候先是调用query方法,处理完1级缓存逻辑后,再去调用doQuery方法,把真正查询数据库的逻辑交给(执行器的三个子类),分别是:SimpleExecutor(简单执行器)(默认执行器),ReuseExecutor(重用执行器),BatchExecutor(批处理执行器)其中一个。

BaseExecutor如何处理一级缓存:

它实现了Executor中的Query与update方法。会话中SQL请求,正是调用的这两个方法。Query方法中处理一级缓存逻辑,即根据SQL及参数判断缓存中是否存在数据,有就走缓存。否则就会调用子类的doQuery() 方法去查询数据库,然后在设置缓存。在doUpdate() 中主要是用于清空缓存。

基础执行器实现了Executor接口,这个接口中有两个比较关键的方法,一个query方法,一个update方法:

1 query方法:在会话请求当中,如果是查询就去调用query方法,query方法中有一级缓存的逻辑,他有缓存,我们就去走缓存,没有缓存的话,那么我们就会去从数据库里面查。查到了以后就将结果集放入一级缓存中。

2 update方法:在update中,针对这个缓存的逻辑主要是去清空这个缓存,只要调用这个update方法,所有的一级缓存在当前会话中都会被全部清空,并不是针对一条数据,是清空全部。

BaseExecutor总结: 1 是简单执行器,重用执行器,批处理执行器的父类,存储着它们的共性功能,即缓存维护和事务管理 2 CachingExecutor二级缓存执行器的delegate属性指向BaseExecutor(根据OOP多态思想,因为是向上转型后的三种执行器之一)

1.2.6 CachingExecutor(二级缓存执行器)

1.2.6.1 源码介绍

1 重要提示 !:close,commit,query,rollback,flushCacheIfRequired方法都内嵌了二级缓存的逻辑

2 关键属性和构造方法:

3 TransactionalCacheManager(事务缓存管理器)中维护了一个HashMap,该HashMap的value可以被称为(暂存区)
1.2.6.2 测试开始的前置条件
UserMapper

//@CacheNamespace 注释掉此注解=未开启二级缓存
public interface UserMapper {

    @Select({" select * from users where id=#{1}"})
    User selectByid(Integer id);

}

测试方法1(执行两次相同SQL的查询,未开启二级缓存,未提交)

@Test
public void cacheExecutorTest() throws SQLException {
     // BaseExecutor,使用简单执行器
     Executor executor = new SimpleExecutor(configuration,jdbcTransaction);
        // 装饰器模式CachingExecutor 装饰了 SimpleExecutor
        // 二级缓存相关逻辑 执行数据操作逻辑
        Executor cachingExecutor=new CachingExecutor(executor);

        cachingExecutor.query(ms, 10, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);

        cachingExecutor.query(ms, 10, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);

}

console(由于未提交,所以只会查询一次SQL):

18:52:55,868 DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource:54 - Created connection 209429254.
18:52:55,869 DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction:54 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@c7ba306]
18:52:55,894 DEBUG org.coderead.mybatis.UserMapper.selectByid:54 - ==>  Preparing: select * from users where id=? 
18:52:55,926 DEBUG org.coderead.mybatis.UserMapper.selectByid:54 - ==> Parameters: 10(Integer)
18:52:55,961 DEBUG org.coderead.mybatis.UserMapper.selectByid:54 - <==      Total: 1

测试方法2(开启二级缓存,未提交,未命中缓存)

@CacheNamespace //解开注释
public interface UserMapper {

    @Select({" select * from users where id=#{1}"})
    User selectByid(Integer id);

}
@Test
public void cacheExecutorTest() throws SQLException {
     // BaseExecutor
     Executor executor = new SimpleExecutor(configuration,jdbcTransaction);
        // 装饰器模式
        //CachingExecutor 装饰了 SimpleExecutor
        // 二级缓存相关逻辑 执行数据操作逻辑
        Executor cachingExecutor=new CachingExecutor(executor);

        cachingExecutor.query(ms, 10, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);

        cachingExecutor.query(ms, 10, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);

}

console:Cache Hit Ratio [org.coderead.mybatis.UserMapper]: 0.0 表示缓存未命中,命中率为0

18:59:43,029 DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource:54 - Created connection 37887172.
18:59:43,029 DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction:54 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2421cc4]
18:59:43,046 DEBUG org.coderead.mybatis.UserMapper:54 - Cache Hit Ratio [org.coderead.mybatis.UserMapper]: 0.0
18:59:43,057 DEBUG org.coderead.mybatis.UserMapper.selectByid:54 - ==>  Preparing: select * from users where id=? 
18:59:43,093 DEBUG org.coderead.mybatis.UserMapper.selectByid:54 - ==> Parameters: 10(Integer)
18:59:43,130 DEBUG org.coderead.mybatis.UserMapper.selectByid:54 - <==      Total: 1
18:59:43,130 DEBUG org.coderead.mybatis.UserMapper:54 - Cache Hit Ratio [org.coderead.mybatis.UserMapper]: 0.0

测试方法3(开启二级缓存,查询一次后提交,命中了缓存)

19:00:54,449 DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource:54 - Created connection 37887172.
19:00:54,449 DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction:54 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2421cc4]
19:00:54,462 DEBUG org.coderead.mybatis.UserMapper:54 - Cache Hit Ratio [org.coderead.mybatis.UserMapper]: 0.0
19:00:54,475 DEBUG org.coderead.mybatis.UserMapper.selectByid:54 - ==>  Preparing: select * from users where id=? 
19:00:54,514 DEBUG org.coderead.mybatis.UserMapper.selectByid:54 - ==> Parameters: 10(Integer)
19:00:54,549 DEBUG org.coderead.mybatis.UserMapper.selectByid:54 - <==      Total: 1
19:00:54,550 DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction:54 - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2421cc4]
19:00:54,569 DEBUG org.coderead.mybatis.UserMapper:54 - Cache Hit Ratio [org.coderead.mybatis.UserMapper]: 0.5

CaChe Hit Ratio 0.5 表示命中缓存

CachingExecutor总结:

CachingExecutor设计使用了装饰器模式,delegate属性指向了BaseExecutor(使用装饰者模式,将三种Executor之一的执行器放入delegate属性中,执行它们的逻辑之前,先执行CachingExecutor的二级缓存逻辑),Mybatis一级缓存必须存在,二级缓存可有可无,装饰者模式使所有执行一级缓存的逻辑之前嵌套了二级缓存的执行逻辑,不得不说,二级缓存的责任链,装饰者等模式设计NB!!!,下一个章节就会讲到,大家拭目以待...

例如:

query方法有二级缓存相关逻辑,所以先执行二级缓存的逻辑

@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) {
          //二级缓存中不存在数据,执行三种基础执行器之一的query方法
          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);
  }

批处理刷新和二级缓存无任何关系,所以不执行二级缓存的相关逻辑

@Override
  public List<BatchResult> flushStatements() throws SQLException {
    return delegate.flushStatements();
  }

二 Mybatis执行器最后陈述

1 SimpleExecutor(简单执行器)(默认执行器):

行为是每次处理会话的SQL请求的过程中,都会通过StatementHandler(Mybatis里面的概念)构建一个新的Statement(JDBC里面的概念),都是通过Statement处理SQL请求。每次都会去构建一个新的Statement,所以说没有办法去重用JDBC里面的Statement,哪怕它们的SQL语句是一样的

如果SqlSession中只有一个执行器若为SimpleExecutor,无论发起多少SQL请求,哪怕SQL语句和参数都是一样的,都会在StatementHandler中创建新的Statement

2 ReuseExecutor(重用执行器)

ReuseExecutor 区别在于他会将在会话期间内的Statement进行缓存,并使用SQL语句作为Key。所以在相同会话内,执行下一请求的时候,不在重复构建Statement,而是从缓存中取出并设置参数,然后执行,提高性能。

3 BatchExecutor(批处理执行器)

BatchExecutor 顾名思议,它就是用来作批处理的。但会将所有会话期间内的所有SQL请求集中起来,最后调用Executor.flushStatements() 方法时一次性将所有请求发送至数据库。

这里它是利用了Statement中的addBath 机制吗?不一定,因为只有连续相同的SQL语句并且相同的SQL映射声明,才会重用Statement,并利用其批处理功能。否则会构建一个新的Satement然后在flushStatements() 时一次执行。 (保证执行顺序,保证调用顺序和执行顺序一定要是一样的,如果不一样...比如两个操作,新增和修改,一旦修改在新增前面,就会执行失败,因为此时还没有这个数据。)

BatchExecutor并不是完全采用了Statement中的adddBatch功能,只能满足相同的SQL语句,而且在连续执行的情况下,才会去重用Statement,利用addBatch功能

4 CachingExecutor(二级缓存执行器)

查看Executor 的子类还有一个CachingExecutor,这是用于处理二级缓存的。为什么不把它和一级缓存一起处理呢?因为二级缓存和一级缓存相对独立的逻辑,而且二级缓存可以通过参数控制关闭,而一级缓存是不可以的。综上原因把二级缓存单独抽出来处理。抽取的方式采用了装饰者设计模式,即在CachingExecutor 对原有的执行器进行包装,处理完二级缓存逻辑之后,把SQL执行相关的逻辑交给实至的Executor处理。