聊聊MyBatis缓存机制 — 二级缓存
# 原理
在《聊聊MyBatis缓存机制 — 一级缓存》提到的一级缓存中,其最大的共享范围就是一个SqlSession内部,如果多个SqlSession之间需要共享缓存,则需要使用到二级缓存。开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询。
二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。其工作流程如下所示:

# 配置
启用二级有两个步骤:
- 在Mybatis配置文件中开启二级缓存:
<setting name="cacheEnabled" value="true"/>
1
- 在Mybatis的Mapper XML文件中声明
cache或cache-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缓存机制 — 一级缓存》中的测试条件。
- 测试二级缓存效果,不提交事务,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
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
显然:缓存命中率0.0,当sqlsession没有调用commit()方法时,二级缓存并没有起到作用。
- 测试二级缓存效果,提交事务,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
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
2
3
4
5
6
7
8
9
10
显然:缓存命中率0.5,当sqlsession_1调用commit()方法时,二级缓存才能起到作用。
- 测试
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
我们可以看到,在sqlSession_3更新数据库,并提交事务后,sqlsession_2的Mapper namespace下的查询走了数据库,没有走Cache。
上次更新: 2020/09/06, 8:09:00