MyBatis缓存

本文最后更新于:2 年前

引言

在高并发和大数据量的应用场景中,缓存机制对于提升性能和降低数据库负担至关重要。MyBatis作为一个流行的持久层框架,提供了灵活的缓存配置选项,允许开发者根据具体需求实施一级缓存和二级缓存策略。此外,MyBatis还支持与第三方缓存框架如EhCache的集成,进一步扩展了缓存的功能和效率。本文将详细介绍这些缓存机制的工作原理和配置方法。

一级缓存(Session Cache)

简介

一级缓存是MyBatis中默认启用的缓存。它的作用范围限定于同一个SqlSession内。当在同一个SqlSession内执行相同的 SQL 查询时,结果会被缓存起来,并在下次查询相同SQL时直接从缓存返回结果,避免了对数据库的重复查询。

  • 作用域:仅限于同一SqlSession
  • 生命周期:与SqlSession的生命周期相同。当SqlSession关闭或执行非查询类SQL时,其一级缓存也会被清空。

配置

一级缓存是自动启用的,无需特别配置。

Demo

在多次查询中观察控制台日志输出(数据库隔离级别为可重复读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession session = sqlSessionFactory.openSession();
StudentMapper studentMapper = session.getMapper(StudentMapper.class);
StudentPO studentPO;
log.info("第1次SqlSession查询:{}", studentMapper.getById(1L));
log.info("第2次相同SqlSession查询:{}", studentPO = studentMapper.getById(1L));

log.info("相同SqlSession更新相同行操作");
studentMapper.updateById(studentPO.setName("zhangsan1"));
log.info("第3次更新相同行后SqlSession查询:{}", studentMapper.getById(1L));
log.info("第4次相同SqlSession查询:{}", studentMapper.getById(1L));

log.info("相同SqlSession更新不同行操作");
studentMapper.updateById(studentPO.setId(2L).setName("zhangsan1"));
log.info("第5次更新不同行后SqlSession查询:{}", studentMapper.getById(1L));
log.info("第6次相同SqlSession查询:{}", studentMapper.getById(1L));

log.info("相同SqlSession更新其它表操作");
VehicleMapper vehicleMapper = session.getMapper(VehicleMapper.class);
vehicleMapper.deleteVehicle(1L);

log.info("第7次更新其它表后SqlSession查询:{}", studentMapper.getById(1L));
log.info("第8次相同SqlSession查询:{}", studentMapper.getById(1L));

log.info("相同SqlSession手动清理缓存");
session.clearCache();

log.info("第9次手动清理缓存后SqlSession查询:{}", studentMapper.getById(1L));
log.info("第10次相同SqlSession查询:{}", studentMapper.getById(1L));

session.commit();
log.info("session提交事务");
log.info("第11次相同SqlSession提交事务后查询:{}", studentMapper.getById(1L));
log.info("第12次相同SqlSession查询:{}", studentMapper.getById(1L));

log.info("不同SqlSession发生更新不同行操作");
SqlSession session2 = sqlSessionFactory.openSession();
StudentMapper studentMapper2 = session2.getMapper(StudentMapper.class);
studentMapper2.updateById(studentPO.setId(3L).setName("zhangsan2"));

session2.commit();
session2.close();

log.info("第13次不同SqlSession更新后查询:{}", studentMapper.getById(1L));
log.info("第14次相同SqlSession查询:{}", studentMapper.getById(1L));

session.rollback();
log.info("session回滚事务");
log.info("第15次回滚后查询:{}", studentMapper.getById(1L));

session2.rollback();
log.info("session2回滚事务");
log.info("第16次不同SqlSession回滚后查询:{}", studentMapper.getById(1L));

session.close();

运行程序,控制台输出日志如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
...
21:30:02.311 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Preparing: select * from tt_student where id = ?
21:30:02.324 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Parameters: 1(Long)
21:30:02.334 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Columns: id, class_id, name, height, gender, birthday, create_time
21:30:02.335 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Row: 1, 1, zhangsan1, 180.0, 男, 1990-01-01, 2022-07-01 22:10:22
21:30:02.336 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - <== Total: 1
21:30:02.337 [main] INFO space.yangtao.client.StudentMapperTest - 第1次SqlSession查询:StudentPO(id=1, classId=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)
21:30:02.338 [main] INFO space.yangtao.client.StudentMapperTest - 第2次相同SqlSession查询:StudentPO(id=1, classId=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)
21:30:02.338 [main] INFO space.yangtao.client.StudentMapperTest - 相同SqlSession更新相同行操作
21:30:02.338 [main] DEBUG space.yangtao.mapper.StudentMapper.updateById - ==> Preparing: update tt_student set name = ?, height = ?, gender = ?, birthday = ?, create_time = ? where id = ?
21:30:02.344 [main] DEBUG space.yangtao.mapper.StudentMapper.updateById - ==> Parameters: zhangsan1(String), 180.0(Double), 男(String), 1990-01-01 00:00:00.0(Timestamp), 2022-07-01 22:10:22.0(Timestamp), 1(Long)
21:30:02.345 [main] DEBUG space.yangtao.mapper.StudentMapper.updateById - <== Updates: 1
21:30:02.345 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Preparing: select * from tt_student where id = ?
21:30:02.345 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Parameters: 1(Long)
21:30:02.346 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Columns: id, class_id, name, height, gender, birthday, create_time
21:30:02.346 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Row: 1, 1, zhangsan1, 180.0, 男, 1990-01-01, 2022-07-01 22:10:22
21:30:02.346 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - <== Total: 1
21:30:02.346 [main] INFO space.yangtao.client.StudentMapperTest - 第3次更新相同行后SqlSession查询:StudentPO(id=1, classId=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)
21:30:02.347 [main] INFO space.yangtao.client.StudentMapperTest - 第4次相同SqlSession查询:StudentPO(id=1, classId=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)
21:30:02.347 [main] INFO space.yangtao.client.StudentMapperTest - 相同SqlSession更新不同行操作
21:30:02.347 [main] DEBUG space.yangtao.mapper.StudentMapper.updateById - ==> Preparing: update tt_student set name = ?, height = ?, gender = ?, birthday = ?, create_time = ? where id = ?
21:30:02.347 [main] DEBUG space.yangtao.mapper.StudentMapper.updateById - ==> Parameters: zhangsan1(String), 180.0(Double), 男(String), 1990-01-01 00:00:00.0(Timestamp), 2022-07-01 22:10:22.0(Timestamp), 2(Long)
21:30:02.348 [main] DEBUG space.yangtao.mapper.StudentMapper.updateById - <== Updates: 1
21:30:02.348 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Preparing: select * from tt_student where id = ?
21:30:02.348 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Parameters: 1(Long)
21:30:02.349 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Columns: id, class_id, name, height, gender, birthday, create_time
21:30:02.349 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Row: 1, 1, zhangsan1, 180.0, 男, 1990-01-01, 2022-07-01 22:10:22
21:30:02.350 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - <== Total: 1
21:30:02.350 [main] INFO space.yangtao.client.StudentMapperTest - 第5次更新不同行后SqlSession查询:StudentPO(id=1, classId=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)
21:30:02.350 [main] INFO space.yangtao.client.StudentMapperTest - 第6次相同SqlSession查询:StudentPO(id=1, classId=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)
21:30:02.350 [main] INFO space.yangtao.client.StudentMapperTest - 相同SqlSession更新其它表操作
21:30:02.351 [main] DEBUG space.yangtao.mapper.VehicleMapper.deleteVehicle - ==> Preparing: delete from tt_vehicle where id = ?
21:30:02.351 [main] DEBUG space.yangtao.mapper.VehicleMapper.deleteVehicle - ==> Parameters: 1(Long)
21:30:02.351 [main] DEBUG space.yangtao.mapper.VehicleMapper.deleteVehicle - <== Updates: 0
21:30:02.352 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Preparing: select * from tt_student where id = ?
21:30:02.352 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Parameters: 1(Long)
21:30:02.352 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Columns: id, class_id, name, height, gender, birthday, create_time
21:30:02.353 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Row: 1, 1, zhangsan1, 180.0, 男, 1990-01-01, 2022-07-01 22:10:22
21:30:02.353 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - <== Total: 1
21:30:02.353 [main] INFO space.yangtao.client.StudentMapperTest - 第7次更新其它表后SqlSession查询:StudentPO(id=1, classId=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)
21:30:02.353 [main] INFO space.yangtao.client.StudentMapperTest - 第8次相同SqlSession查询:StudentPO(id=1, classId=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)
21:30:02.354 [main] INFO space.yangtao.client.StudentMapperTest - 相同SqlSession手动清理缓存
21:30:02.354 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Preparing: select * from tt_student where id = ?
21:30:02.354 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Parameters: 1(Long)
21:30:02.355 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Columns: id, class_id, name, height, gender, birthday, create_time
21:30:02.355 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Row: 1, 1, zhangsan1, 180.0, 男, 1990-01-01, 2022-07-01 22:10:22
21:30:02.355 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - <== Total: 1
21:30:02.356 [main] INFO space.yangtao.client.StudentMapperTest - 第9次手动清理缓存后SqlSession查询:StudentPO(id=1, classId=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)
21:30:02.356 [main] INFO space.yangtao.client.StudentMapperTest - 第10次相同SqlSession查询:StudentPO(id=1, classId=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)
21:30:02.356 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@a22cb6a]
21:30:02.357 [main] INFO space.yangtao.client.StudentMapperTest - session提交事务
21:30:02.357 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Preparing: select * from tt_student where id = ?
21:30:02.357 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Parameters: 1(Long)
21:30:02.358 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Columns: id, class_id, name, height, gender, birthday, create_time
21:30:02.358 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Row: 1, 1, zhangsan1, 180.0, 男, 1990-01-01, 2022-07-01 22:10:22
21:30:02.358 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - <== Total: 1
21:30:02.358 [main] INFO space.yangtao.client.StudentMapperTest - 第11次相同SqlSession提交事务后查询:StudentPO(id=1, classId=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)
21:30:02.358 [main] INFO space.yangtao.client.StudentMapperTest - 第12次相同SqlSession查询:StudentPO(id=1, classId=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)
21:30:02.358 [main] INFO space.yangtao.client.StudentMapperTest - 不同SqlSession发生更新不同行操作
21:30:02.359 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
21:30:02.369 [main] DEBUG o.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 1976401987.
21:30:02.369 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@75cd8043]
21:30:02.370 [main] DEBUG space.yangtao.mapper.StudentMapper.updateById - ==> Preparing: update tt_student set name = ?, height = ?, gender = ?, birthday = ?, create_time = ? where id = ?
21:30:02.370 [main] DEBUG space.yangtao.mapper.StudentMapper.updateById - ==> Parameters: zhangsan2(String), 180.0(Double), 男(String), 1990-01-01 00:00:00.0(Timestamp), 2022-07-01 22:10:22.0(Timestamp), 3(Long)
21:30:02.371 [main] DEBUG space.yangtao.mapper.StudentMapper.updateById - <== Updates: 1
21:30:02.371 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@75cd8043]
21:30:02.371 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@75cd8043]
21:30:02.372 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@75cd8043]
21:30:02.372 [main] DEBUG o.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 1976401987 to pool.
21:30:02.372 [main] INFO space.yangtao.client.StudentMapperTest - 第13次不同SqlSession更新后查询:StudentPO(id=1, classId=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)
21:30:02.372 [main] INFO space.yangtao.client.StudentMapperTest - 第14次相同SqlSession查询:StudentPO(id=1, classId=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)
21:30:02.372 [main] INFO space.yangtao.client.StudentMapperTest - session回滚事务
21:30:02.372 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Preparing: select * from tt_student where id = ?
21:30:02.372 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Parameters: 1(Long)
21:30:02.373 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Columns: id, class_id, name, height, gender, birthday, create_time
21:30:02.373 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Row: 1, 1, zhangsan1, 180.0, 男, 1990-01-01, 2022-07-01 22:10:22
21:30:02.373 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - <== Total: 1
21:30:02.373 [main] INFO space.yangtao.client.StudentMapperTest - 第15次回滚后查询:StudentPO(id=1, classId=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)
21:30:02.373 [main] INFO space.yangtao.client.StudentMapperTest - session2回滚事务
21:30:02.373 [main] INFO space.yangtao.client.StudentMapperTest - 第16次不同SqlSession回滚后查询:StudentPO(id=1, classId=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)
...

日志分析

  1. 第1次和第2次使用相同的SqlSession,因此第2次查询直接从缓存中取数据。
  2. 第3次查询前发生了SqlSession更新相同行操作,因此查询没使用缓存,而第4次查询使用了缓存。
  3. 第5次查询前发生了SqlSession更新不同行操作,因此查询没使用缓存,而第6次查询使用了缓存。
  4. 第7次查询前发生了SqlSession更新其他表操作,因此查询没使用缓存,而第8次查询使用了缓存。
  5. 第9次查询前手动清除了SqlSession的缓存,因此查询没有使用缓存,而第10次查询使用了缓存。
  6. 第11次查询前SqlSession提交了一次事务,因此查询没有使用缓存,而第12次查询使用了缓存。
  7. 第13次查询前其他SqlSession发生了更新不同行的操作,但不影响本SqlSession,因此第13次和第14次查询都使用了缓存。
  8. 第15次查询前SqlSession回滚了事务,因此查询没使用缓存,而后不同SqlSession回滚了事务,但不影响本SqlSession,因此第16次查询使用了缓存。
  9. 注意这里隐藏了一个场景,其他SqlSession更新了相同行,因为更新相同行的条件是本SqlSession提交事务,否则MySQL的行锁会导致其他SqlSession更新相同行失败,因此结果跟本次SqlSession提交事务后的结果是一致的,即提交了事务后本次SqlSession的查询不使用缓存。

总结:在默认的一级缓存中,相同的事务中SqlSession只要执行了非查询类的操作(更新、删除、事务提交或回滚),就会导致本次事务中的所有缓存都失效,在可重复度的隔离级别下,其他事务不会影响本次事务,因此不会影响本次事务的缓存。

二级缓存(Mapper Cache)

简介

二级缓存的作用范围更广,可以跨多个SqlSessionSqlMapper。二级缓存需要显式地在MyBatis配置文件中或通过注解启用。启用后,多个会话可以重用缓存的数据,大大减少数据库的查询次数,提高了应用性能。

  • 作用域:跨SqlSession,通常绑定到一个Mapper接口。
  • 生命周期:通常跟应用的生命周期一致,或者是缓存提供者(如Ehcache、Redis)管理的。
  • 配置:需要在MyBatis的配置文件中启用,并且可以对其细粒度控制,例如配置缓存的大小、清除策略、读写策略等。

配置

  1. 全局配置文件中开启(默认开启)

    1
    <setting name="cacheEnabled" value="true"/>
  2. Mapper文件中加上<cache/>标签表示局部开启

    1
    <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
    • eviction:缓存的回收策略。默认为LRU策略。

      • LRU:Least Recently Used,最近最少使用。

      • FIFO:First In First Out,先进先出。

      • SOFT:软引用指向对象优先淘汰。

      • WEAK:弱引用指向对象优先淘汰。

    • flushInterval:二级缓存的刷新时间间隔,单位为毫秒,没有则代表不刷新缓存。

    • readOnly

      • true:多条相同语句执行返回的对象是共享的一个,性能高,可能有并发问题。

      • false:多条相同语句执行返回的对象是克隆出来的副本,性能一般,多线程安全。

    • size:引用数目,一个数值,表示缓存中可以存储的引用数量,默认1024。

DEMO

在多次查询中观察控制台日志输出(数据库隔离级别为可重复读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession session1 = sqlSessionFactory.openSession();
SqlSession session2 = sqlSessionFactory.openSession();

StudentMapper mapper1 = session1.getMapper(StudentMapper.class);
StudentMapper mapper2 = session2.getMapper(StudentMapper.class);

log.info("session1 第1次查询:{}", mapper1.getById(1L));
log.info("session2 第1次查询:{}", mapper2.getById(1L));

session1.commit();
session2.clearCache();

log.info("session2 第2次查询:{}", mapper2.getById(1L));
session2.clearCache();

log.info("session2 第3次查询:{}", mapper2.getById(1L));
VehicleMapper vehicleMapper = session1.getMapper(VehicleMapper.class);
vehicleMapper.deleteVehicle(1L);
session1.commit();
session2.commit();
log.info("session2 第4次查询:{}", mapper2.getById(1L));
session2.clearCache();

mapper1.updateById(mapper1.getById(3L).setId(2L).setName("zhangsan1"));
log.info("session2 第5次查询:{}", mapper2.getById(1L));
session1.commit();

log.info("session2 第6次查询:{}", mapper2.getById(1L));
session2.close();
session1.clearCache();

log.info("session1 第2次查询:{}", mapper1.getById(1L));
log.info("session1 第3次查询:{}", mapper1.getById(1L));

运行结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
...
22:22:04.491 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Preparing: select * from tt_student where id = ?
22:22:04.505 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Parameters: 1(Long)
22:22:04.516 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Columns: id, class_id, name, height, gender, birthday, create_time
22:22:04.516 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Row: 1, 1, zhangsan1, 180.0, 男, 1990-01-01, 2022-07-01 22:10:22
22:22:04.518 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - <== Total: 1
22:22:04.519 [main] INFO space.yangtao.client.StudentMapperTest - session1 第1次查询:StudentPO(id=1, classId=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)
22:22:04.520 [main] DEBUG space.yangtao.mapper.StudentMapper - Cache Hit Ratio [space.yangtao.mapper.StudentMapper]: 0.0
22:22:04.520 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
22:22:04.530 [main] DEBUG o.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 1653361344.
22:22:04.530 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@628c4ac0]
22:22:04.530 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Preparing: select * from tt_student where id = ?
22:22:04.530 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Parameters: 1(Long)
22:22:04.531 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Columns: id, class_id, name, height, gender, birthday, create_time
22:22:04.531 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Row: 1, 1, zhangsan1, 180.0, 男, 1990-01-01, 2022-07-01 22:10:22
22:22:04.531 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - <== Total: 1
22:22:04.532 [main] INFO space.yangtao.client.StudentMapperTest - session2 第1次查询:StudentPO(id=1, classId=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)
22:22:04.534 [main] WARN org.apache.ibatis.io.SerialFilterChecker - As you are using functionality that deserializes object streams, it is recommended to define the JEP-290 serial filter. Please refer to https://docs.oracle.com/pls/topic/lookup?ctx=javase15&id=GUID-8296D8E8-2B93-4B9A-856E-0A65AF9B8C66
22:22:04.536 [main] DEBUG space.yangtao.mapper.StudentMapper - Cache Hit Ratio [space.yangtao.mapper.StudentMapper]: 0.3333333333333333
22:22:04.536 [main] INFO space.yangtao.client.StudentMapperTest - session2 第2次查询:StudentPO(id=1, classId=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)
22:22:04.536 [main] DEBUG space.yangtao.mapper.StudentMapper - Cache Hit Ratio [space.yangtao.mapper.StudentMapper]: 0.5
22:22:04.536 [main] INFO space.yangtao.client.StudentMapperTest - session2 第3次查询:StudentPO(id=1, classId=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)
22:22:04.537 [main] DEBUG space.yangtao.mapper.VehicleMapper.deleteVehicle - ==> Preparing: delete from tt_vehicle where id = ?
22:22:04.537 [main] DEBUG space.yangtao.mapper.VehicleMapper.deleteVehicle - ==> Parameters: 1(Long)
22:22:04.538 [main] DEBUG space.yangtao.mapper.VehicleMapper.deleteVehicle - <== Updates: 0
22:22:04.538 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@79c7532f]
22:22:04.538 [main] DEBUG space.yangtao.mapper.StudentMapper - Cache Hit Ratio [space.yangtao.mapper.StudentMapper]: 0.6
22:22:04.538 [main] INFO space.yangtao.client.StudentMapperTest - session2 第4次查询:StudentPO(id=1, classId=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)
22:22:04.539 [main] DEBUG space.yangtao.mapper.StudentMapper - Cache Hit Ratio [space.yangtao.mapper.StudentMapper]: 0.5
22:22:04.539 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Preparing: select * from tt_student where id = ?
22:22:04.539 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Parameters: 3(Long)
22:22:04.539 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Columns: id, class_id, name, height, gender, birthday, create_time
22:22:04.540 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Row: 3, 2, zhangsan2, 180.0, 男, 1990-01-01, 2022-07-01 22:10:22
22:22:04.540 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - <== Total: 1
22:22:04.540 [main] DEBUG space.yangtao.mapper.StudentMapper.updateById - ==> Preparing: update tt_student set name = ?, height = ?, gender = ?, birthday = ?, create_time = ? where id = ?
22:22:04.546 [main] DEBUG space.yangtao.mapper.StudentMapper.updateById - ==> Parameters: zhangsan1(String), 180.0(Double), 男(String), 1990-01-01 00:00:00.0(Timestamp), 2022-07-01 22:10:22.0(Timestamp), 2(Long)
22:22:04.547 [main] DEBUG space.yangtao.mapper.StudentMapper.updateById - <== Updates: 1
22:22:04.548 [main] DEBUG space.yangtao.mapper.StudentMapper - Cache Hit Ratio [space.yangtao.mapper.StudentMapper]: 0.5714285714285714
22:22:04.548 [main] INFO space.yangtao.client.StudentMapperTest - session2 第5次查询:StudentPO(id=1, classId=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)
22:22:04.548 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@79c7532f]
22:22:04.548 [main] DEBUG space.yangtao.mapper.StudentMapper - Cache Hit Ratio [space.yangtao.mapper.StudentMapper]: 0.5
22:22:04.548 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Preparing: select * from tt_student where id = ?
22:22:04.548 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Parameters: 1(Long)
22:22:04.549 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Columns: id, class_id, name, height, gender, birthday, create_time
22:22:04.549 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Row: 1, 1, zhangsan1, 180.0, 男, 1990-01-01, 2022-07-01 22:10:22
22:22:04.550 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - <== Total: 1
22:22:04.551 [main] INFO space.yangtao.client.StudentMapperTest - session2 第6次查询:StudentPO(id=1, classId=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)
22:22:04.551 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@628c4ac0]
22:22:04.552 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@628c4ac0]
22:22:04.552 [main] DEBUG o.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 1653361344 to pool.
22:22:04.552 [main] DEBUG space.yangtao.mapper.StudentMapper - Cache Hit Ratio [space.yangtao.mapper.StudentMapper]: 0.5555555555555556
22:22:04.552 [main] INFO space.yangtao.client.StudentMapperTest - session1 第2次查询:StudentPO(id=1, classId=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)
22:22:04.552 [main] DEBUG space.yangtao.mapper.StudentMapper - Cache Hit Ratio [space.yangtao.mapper.StudentMapper]: 0.5
22:22:04.552 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Preparing: select * from tt_student where id = ?
22:22:04.553 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Parameters: 2(Long)
22:22:04.553 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Columns: id, class_id, name, height, gender, birthday, create_time
22:22:04.553 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Row: 2, 2, zhangsan1, 180.0, 男, 1990-01-01, 2022-07-01 22:10:22
22:22:04.554 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - <== Total: 1
22:22:04.554 [main] INFO space.yangtao.client.StudentMapperTest - session1 第3次查询:StudentPO(id=2, classId=2, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)
...

日志分析

  1. 由于session1第1次查询后未提交或关闭session,因此session2的查询不是从二级缓存中取数据,而是从磁盘中取数据。
  2. session1提交、同时清除session2的一级缓存,因此session2的第2次查询从二级缓存中取数据。
  3. session2在第2次查询后就清除了一级缓存,第3次查询仍然是从缓存中取数据,因此可以判定缓存为二级缓存。
  4. session1更新其它的数据表并提交事务、session2重新开启事务,第4次查询仍然从二级缓存中取数据。
  5. session1更新相同表的其他行数据,未提交事务前,session2的第5次查询从二级缓存中取数据。
  6. session1提交事务后,二级缓存失效,session2的第6次查询重新从磁盘中读取数据。
  7. session2关闭,此时将session1的一级缓存清除,查询仍然从二级缓存中取数据,而对于刚才更新并提交事务的相同行,则会重新从磁盘中取数据。

总结:在原有一级缓存的规则下,其他session的事务提交或会话关闭会将其一级缓存写入到二级缓存中,且未有session提交此表相同行的事务的前提下,此表中该行的二级缓存会一直存在于SqlSessionFactory中。

集成EhCache

简介

EhCache是一个广泛使用的开源Java分布式缓存框架,提供了内存和磁盘存储的缓存功能,非常适合用作MyBatis的二级缓存。通过集成EhCache,MyBatis可以提升应用性能,减少数据库访问次数,以及提供更复杂的缓存管理功能。

集成EcCache可以替换MyBatis自带的二级缓存(一级缓存无法替代)。

DEMO

  1. 引入依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.3</version>
    </dependency>
  2. 类的根路径下创建配置文件ehcache.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <?xml version="1.0" encoding="UTF-8"?>
    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
    updateCheck="false">
    <!--磁盘存储:将缓存中暂时不使用的对象,转移到硬盘,类似于Windows系统的虚拟内存。-->
    <diskStore path="e:/ehcache"/>

    <!--defaultCache:默认的管理策略-->
    <!--eternal:设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断-->
    <!--maxElementsInMemory:在内存中缓存的element的最大数目-->
    <!--overflowToDisk:如果内存中数据超过内存限制,是否要缓存到磁盘上-->
    <!--diskPersistent:是否在磁盘上持久化。指重启jvm后,数据是否有效。默认为false-->
    <!--timeToIdleSeconds:对象空闲时间(单位:秒),指对象在多长时间没有被访问就会失效。只对eternal为false的有效。默认值0,表示一直可以访问-->
    <!--timeToLiveSeconds:对象存活时间(单位:秒),指对象从创建到失效所需要的时间。只对eternal为false的有效。默认值0,表示一直可以访问-->
    <!--memoryStoreEvictionPolicy:缓存的3 种清空策略-->
    <!--FIFO:first in first out (先进先出)-->
    <!--LFU:Less Frequently Used (最少使用).意思是一直以来最少被使用的。缓存的元素有一个hit 属性,hit 值最小的将会被清出缓存-->
    <!--LRU:Least Recently Used(最近最少使用). (ehcache 默认值).缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存-->
    <defaultCache eternal="false" maxElementsInMemory="1000" overflowToDisk="false" diskPersistent="false"
    timeToIdleSeconds="0" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU"/>

    </ehcache>
  3. 在Mapper文件中指定使用EhCache

    1
    <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
  4. 运行测试程序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
    SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
    SqlSession session1 = sqlSessionFactory.openSession();
    SqlSession session2 = sqlSessionFactory.openSession();

    StudentMapper mapper1 = session1.getMapper(StudentMapper.class);
    StudentMapper mapper2 = session2.getMapper(StudentMapper.class);

    log.info("session1 第1次查询:{}", mapper1.getById(1L));
    log.info("session2 第1次查询:{}", mapper2.getById(1L));

    session1.close();
    session2.clearCache();

    log.info("session2 第2次查询:{}", mapper2.getById(1L));
  5. 控制台输出如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    22:40:41.562 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
    22:40:42.323 [main] DEBUG o.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 1205559205.
    22:40:42.323 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@47db5fa5]
    22:40:42.325 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Preparing: select * from tt_student where id = ?
    22:40:42.350 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Parameters: 1(Long)
    22:40:42.362 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Columns: id, class_id, name, height, gender, birthday, create_time
    22:40:42.363 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Row: 1, 1, zhangsan1, 180.0, 男, 1990-01-01, 2022-07-01 22:10:22
    22:40:42.365 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - <== Total: 1
    22:40:42.367 [main] INFO space.yangtao.client.StudentMapperTest - session1 第1次查询:StudentPO(id=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)
    22:40:42.367 [main] DEBUG space.yangtao.mapper.StudentMapper - Cache Hit Ratio [space.yangtao.mapper.StudentMapper]: 0.0
    22:40:42.367 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
    22:40:42.381 [main] DEBUG o.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 367967231.
    22:40:42.382 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@15eebbff]
    22:40:42.382 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Preparing: select * from tt_student where id = ?
    22:40:42.382 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Parameters: 1(Long)
    22:40:42.383 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Columns: id, class_id, name, height, gender, birthday, create_time
    22:40:42.383 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Row: 1, 1, zhangsan1, 180.0, 男, 1990-01-01, 2022-07-01 22:10:22
    22:40:42.384 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - <== Total: 1
    22:40:42.384 [main] INFO space.yangtao.client.StudentMapperTest - session2 第1次查询:StudentPO(id=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)
    22:40:42.385 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@47db5fa5]
    22:40:42.385 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@47db5fa5]
    22:40:42.385 [main] DEBUG o.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 1205559205 to pool.
    22:40:42.385 [main] DEBUG space.yangtao.mapper.StudentMapper - Cache Hit Ratio [space.yangtao.mapper.StudentMapper]: 0.3333333333333333
    22:40:42.385 [main] INFO space.yangtao.client.StudentMapperTest - session2 第2次查询:StudentPO(id=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)

    由日志可知,MyBatis已集成并使用EhCache

注意:建议提醒读者在实际应用中,需要根据应用的并发量和数据量,合理配置缓存大小,以防止内存溢出或缓存失效等问题。

总结

通过本文的学习,开发者可以充分理解并运用MyBatis提供的缓存机制,有效地管理SqlSession的生命周期和跨会话的数据共享。一级缓存带来了会话内的数据访问优化,而二级缓存则扩展了数据共享的范围,提高了应用的响应速度和数据处理能力。此外,通过EhCache的集成,开发者能够实现更复杂的缓存策略,如数据的持久化和过期策略管理。掌握这些技能将显著提升数据库操作的效率和应用的性能。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!