聊聊MyBatis缓存机制 — 二级缓存

# 原理

《聊聊MyBatis缓存机制 — 一级缓存》提到的一级缓存中,其最大的共享范围就是一个SqlSession内部,如果多个SqlSession之间需要共享缓存,则需要使用到二级缓存。开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询。

二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。其工作流程如下所示:

# 配置

启用二级有两个步骤:

  1. 在Mybatis配置文件中开启二级缓存:
<setting name="cacheEnabled" value="true"/>
1
  1. 在Mybatis的Mapper XML文件中声明cachecache-ref:
<cache/>
1

cache标签用于声明这个namespace使用二级缓存,并且可以自定义配置。

  • type:cache使用的类型,默认是PerpetualCache,这在一级缓存中提到过。
  • eviction: 定义回收的策略,常见的有FIFO,LRU。
  • flushInterval: 配置一定时间自动刷新缓存,单位是毫秒。
  • size: 最多缓存对象的个数。
  • readOnly: 是否只读,若配置可读写,则需要对应的实体类能够序列化。
  • blocking: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。

cache-ref代表引用别的命名空间的Cache配置,两个命名空间的操作使用的是同一个Cache。

<cache-ref namespace="mapper.StudentMapper"/>
1

# 测试

延续《聊聊MyBatis缓存机制 — 一级缓存》中的测试条件。

  1. 测试二级缓存效果,不提交事务,sqlSession_1查询完数据后,sqlSession_2相同的查询是否会从缓存中获取数据。
@Test
  public void localCacheCachingExecutorNotCommit() {
      try(SqlSession session_1 = sqlSessionFactory.openSession(true);
          SqlSession session_2 = sqlSessionFactory.openSession(true)) {
          UserMapper userMapper_1 = session_1.getMapper(UserMapper.class);
          UserMapper userMapper_2 = session_2.getMapper(UserMapper.class);

          System.out.println("userMapper_1 " + userMapper_1.selectUserById(4));
          System.out.println("userMapper_2 " + userMapper_2.selectUserById(4));
      }
  }
1
2
3
4
5
6
7
8
9
10
11

输出:

==>  Preparing: select * from user where id = ?; 
==> Parameters: 4(Integer)
<==    Columns: id, username, password, age
<==        Row: 4, Zhang san, 123, 34
<==      Total: 1
userMapper_1 mapper.User@17aad511

Cache Hit Ratio [mapper.UserMapper]: 0.0

==>  Preparing: select * from user where id = ?; 
==> Parameters: 4(Integer)
<==    Columns: id, username, password, age
<==        Row: 4, Zhang san, 123, 34
<==      Total: 1
userMapper_2 mapper.User@79c7532f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

显然:缓存命中率0.0,当sqlsession没有调用commit()方法时,二级缓存并没有起到作用

  1. 测试二级缓存效果,提交事务,sqlSession_1查询完数据后,sqlSession_2相同的查询是否会从缓存中获取数据。
@Test
public void localCacheCachingExecutorCommit() {
    try(SqlSession session_1 = sqlSessionFactory.openSession(true);
        SqlSession session_2 = sqlSessionFactory.openSession(true)) {
        UserMapper userMapper_1 = session_1.getMapper(UserMapper.class);
        UserMapper userMapper_2 = session_2.getMapper(UserMapper.class);

        System.out.println("userMapper_1 " + userMapper_1.selectUserById(4));
        session_1.commit();
        System.out.println("userMapper_2 " + userMapper_2.selectUserById(4));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
==>  Preparing: select * from user where id = ?; 
==> Parameters: 4(Integer)
<==    Columns: id, username, password, age
<==        Row: 4, Zhang san, 123, 34
<==      Total: 1
userMapper_1 mapper.User@5dd1c9f2
    
Cache Hit Ratio [mapper.UserMapper]: 0.5
    
userMapper_2 mapper.User@4879dfad
1
2
3
4
5
6
7
8
9
10

显然:缓存命中率0.5,当sqlsession_1调用commit()方法时,二级缓存才能起到作用

  1. 测试update操作是否会刷新该namespace下的二级缓存。

1
==>  Preparing: select * from user where id = ?; 
==> Parameters: 4(Integer)
<==    Columns: id, username, password, age
<==        Row: 4, Zhang san, 123, 34
<==      Total: 1
userMapper_1 mapper.User@46944ca9
Cache Hit Ratio [mapper.UserMapper]: 0.5
userMapper_2 mapper.User@13d9b21f

==>  Preparing: update user set username = ? where id = ?; 
==> Parameters: Wang wu(String), 4(Integer)
<==    Updates: 1
Cache Hit Ratio [mapper.UserMapper]: 0.3333333333333333
  
==>  Preparing: select * from user where id = ?; 
==> Parameters: 4(Integer)
<==    Columns: id, username, password, age
<==        Row: 4, Wang wu, 123, 34
<==      Total: 1
userMapper_2 mapper.User@79d94571
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

我们可以看到,在sqlSession_3更新数据库,并提交事务后,sqlsession_2Mapper namespace下的查询走了数据库,没有走Cache。

上次更新: 2020/09/06, 8:09:00
最近更新
01
RabbitMQ简介
10-27
02
聊聊Java多态
10-21
03
JVM垃圾回收器
10-16
更多文章>