Mybatis 缓存实战
数据准备
建表脚本
- db.sql
CREATE DATABASE mybatis
DEFAULT CHARACTER SET utf8
COLLATE utf8_general_ci;
USE mybatis;
- init.sql
DROP TABLE IF EXISTS `user`;
CREATE TABLE user (
id BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL
COMMENT '主键, 自增',
username VARCHAR(64) NOT NULL
COMMENT '用户名',
password VARCHAR(128) NOT NULL
COMMENT '密码',
`created_time` timestamp NULL,
`updated_time` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE INDEX `username_UNIQUE` (`username`)
)
COMMENT '用户表';
INSERT INTO `user` (username, password, created_time, updated_time) VALUES (
'ryo', '123456', NOW(), NOW()
);
DROP TABLE IF EXISTS `role`;
CREATE TABLE role (
id BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL
COMMENT '主键,自增',
name VARCHAR(64) NOT NULL
COMMENT '角色名称',
code VARCHAR(64) NOT NULL
COMMENT '角色代码',
description VARCHAR(128) NULL DEFAULT ''
COMMENT '角色说明',
`created_time` timestamp NULL,
`updated_time` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX `name`(`name`),
UNIQUE INDEX `code_UNIQUE`(`code`)
)
COMMENT '角色表';
INSERT INTO `role` (name, code, description, created_time, updated_time) VALUES (
'管理员', 'admin', '这个系统里天下第一', NOW(), NOW()
);
开启测试
mybatis 配置
- mybatis-config.xml
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!--指定日志为log4j2-->
<setting name="logImpl" value="LOG4J2" />
<setting name="localCacheScope" value="SESSION"/>
</settings>
</configuration>
其他配置省略。
测试一
- 条件
一级缓存开启 + localCacheScope=SESSION
- 测试代码
在同一个 SqlSession 执行两次相同条件的查询。
@Test
public void timesQueryTest() {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setId(1L);
System.out.println("初次 " + userMapper.selectOne(user));
System.out.println("再次 " + userMapper.selectOne(user));
}
- 测试结果
实际上只执行了一次。第二次直接使用缓存的结果。
2018-09-08 13:04:04.109 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.UserMapper.selectOne:145 - ==> Preparing: SELECT ID,USERNAME,PASSWORD,created_time,updated_time FROM uSER WHERE ID = ?
2018-09-08 13:04:04.133 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.UserMapper.selectOne:145 - ==> Parameters: 1(Long)
2018-09-08 13:04:04.150 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.UserMapper.selectOne:145 - <== Total: 1
初次 com.github.houbb.mybatis.learn.cache.model.User@201b6b6f
再次 com.github.houbb.mybatis.learn.cache.model.User@201b6b6f
- 实际场景
如果在 spring 事务代码中,我们写了两个相同的数据库调用。
比如 sn.nextVal()
调用了两次,第二次可能直接就不会调用数据库。
测试二
- 条件
一级缓存开启 + localCacheScope=SESSION
- 测试代码
在同一个 SqlSession 执行两次相同条件的查询。
但是中间添加一个额外的数据库入库操作。
@Test
public void queryUpdateQueryTest() {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setId(1L);
System.out.println("初次 " + userMapper.selectOne(user));
User userNew = new User();
userNew.setUsername("new");
userNew.setPassword("123456");
System.out.println("添加了新用户: " + userNew.getUsername());
userMapper.insert(userNew);
System.out.println("再次 " + userMapper.selectOne(user));
}
- 日志
这次是调用了 2 次。
说明如果中间有更新操作,则清空了本地缓存。
2018-09-08 13:14:29.105 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.UserMapper.selectOne:145 - ==> Preparing: SELECT ID,USERNAME,PASSWORD,created_time,updated_time FROM uSER WHERE ID = ?
2018-09-08 13:14:29.127 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.UserMapper.selectOne:145 - ==> Parameters: 1(Long)
2018-09-08 13:14:29.143 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.UserMapper.selectOne:145 - <== Total: 1
初次 com.github.houbb.mybatis.learn.cache.model.User@75459c75
添加了新用户: new
2018-09-08 13:14:29.151 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.UserMapper.insert:145 - ==> Preparing: INSERT INTO uSER ( ID,USERNAME,PASSWORD,created_time,updated_time ) VALUES( ?,?,?,?,? )
2018-09-08 13:14:29.151 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.UserMapper.insert:145 - ==> Parameters: null, new(String), 123456(String), null, null
2018-09-08 13:14:29.157 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.UserMapper.insert:145 - <== Updates: 1
2018-09-08 13:14:29.160 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.UserMapper.insert!selectKey:145 - ==> Executing: SELECT LAST_INSERT_ID()
2018-09-08 13:14:29.161 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.UserMapper.insert!selectKey:145 - <== Total: 1
2018-09-08 13:14:29.162 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.UserMapper.selectOne:145 - ==> Preparing: SELECT ID,USERNAME,PASSWORD,created_time,updated_time FROM uSER WHERE ID = ?
2018-09-08 13:14:29.162 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.UserMapper.selectOne:145 - ==> Parameters: 1(Long)
2018-09-08 13:14:29.164 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.UserMapper.selectOne:145 - <== Total: 1
再次 com.github.houbb.mybatis.learn.cache.model.User@4c4d27c8
测试三
- 测试代码
开启两个SqlSession,在sqlSession1中查询数据,使一级缓存生效,在sqlSession2中更新数据库,验证一级缓存只在数据库会话内部共享。
@Test
public void twoSessionQueryUpdateQueryTest() {
final Long userKey = 1L;
SqlSession firstSqlSession = sqlSessionFactory.openSession(true);
SqlSession secondSqlSession = sqlSessionFactory.openSession(true);
UserMapper firstUserMapper = firstSqlSession.getMapper(UserMapper.class);
UserMapper secondUserMapper = secondSqlSession.getMapper(UserMapper.class);
System.out.println("firstUserMapper 初次 " + firstUserMapper.selectByPrimaryKey(userKey));
// 另一个 session 更新
User userUpdate = new User();
userUpdate.setId(userKey);
userUpdate.setPassword("new " + RandomStringUtils.randomNumeric(5));
System.out.println("secondUserMapper 更新: " + secondUserMapper.updateByPrimaryKeySelective(userUpdate));
System.out.println("firstUserMapper 再次 " + firstUserMapper.selectByPrimaryKey(userKey));
System.out.println("secondUserMapper 查询 " + secondUserMapper.selectByPrimaryKey(userKey));
}
- 日志
firstUserMapper 查询到的是脏数据。
secondSqlSession 的更新并没有清空 firstSqlSession 的本地一级缓存。
2018-09-08 14:12:36.465 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.UserMapper.selectByPrimaryKey:145 - ==> Preparing: SELECT ID,USERNAME,PASSWORD,created_time,updated_time FROM uSER WHERE ID = ?
2018-09-08 14:12:36.485 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.UserMapper.selectByPrimaryKey:145 - ==> Parameters: 1(Long)
2018-09-08 14:12:36.503 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.UserMapper.selectByPrimaryKey:145 - <== Total: 1
firstUserMapper 初次 User{id=1, username='ryo', password='new 10098', createdTime=Sat Sep 08 04:37:10 CST 2018, updatedTime=Sat Sep 08 04:37:10 CST 2018}
2018-09-08 14:12:36.529 DEBUG [main] org.springframework.jdbc.datasource.DataSourceUtils:114 - Fetching JDBC Connection from DataSource
2018-09-08 14:12:36.543 DEBUG [main] org.mybatis.spring.transaction.SpringManagedTransaction:89 - JDBC Connection [jdbc:mysql://127.0.0.1:13306/mybatis?useUnicode=true&characterEncoding=UTF-8&useOldAlias, UserName=root@172.17.0.1, MySQL Connector Java] will not be managed by Spring
2018-09-08 14:12:36.543 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.UserMapper.updateByPrimaryKeySelective:145 - ==> Preparing: UPDATE uSER SET PASSWORD = ? WHERE ID = ?
2018-09-08 14:12:36.544 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.UserMapper.updateByPrimaryKeySelective:145 - ==> Parameters: new 99507(String), 1(Long)
2018-09-08 14:12:36.548 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.UserMapper.updateByPrimaryKeySelective:145 - <== Updates: 1
secondUserMapper 更新: 1
firstUserMapper 再次 User{id=1, username='ryo', password='new 10098', createdTime=Sat Sep 08 04:37:10 CST 2018, updatedTime=Sat Sep 08 04:37:10 CST 2018}
2018-09-08 14:12:36.549 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.UserMapper.selectByPrimaryKey:145 - ==> Preparing: SELECT ID,USERNAME,PASSWORD,created_time,updated_time FROM uSER WHERE ID = ?
2018-09-08 14:12:36.549 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.UserMapper.selectByPrimaryKey:145 - ==> Parameters: 1(Long)
2018-09-08 14:12:36.550 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.UserMapper.selectByPrimaryKey:145 - <== Total: 1
secondUserMapper 查询 User{id=1, username='ryo', password='new 99507', createdTime=Sat Sep 08 04:37:10 CST 2018, updatedTime=Sat Sep 08 06:12:36 CST 2018}
一级缓存流程
二级缓存
介绍
在上文中提到的一级缓存中,其最大的共享范围就是一个SqlSession内部,如果多个SqlSession之间需要共享缓存,则需要使用到二级缓存。
开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询,具体的工作流程如下所示。
二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。 当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。
二级缓存配置
- 全局开启配置
<setting name="cacheEnabled" value="true"/>
- 在 xml 文件中配置
在 MyBatis 的映射 XML 中配置 cach e或者 cache-ref 。
<cache/>
or
cache-ref代表引用别的命名空间的Cache配置,两个命名空间的操作使用的是同一个Cache。
<cache-ref namespace="com.github.houbb.mybatis.learn.cache.mapper.UserMapper"/>
实验一
- 场景
两个 session,第一个不提交
- 代码
@Test
public void queryTest() {
final Long roleKey = 1L;
SqlSession firstSqlSession = sqlSessionFactory.openSession(true);
SqlSession secondSqlSession = sqlSessionFactory.openSession(true);
RoleMapper firstMapper = firstSqlSession.getMapper(RoleMapper.class);
RoleMapper secondMapper = secondSqlSession.getMapper(RoleMapper.class);
System.out.println("firstUserMapper 查询:" + firstMapper.selectByPrimaryKey(roleKey));
System.out.println("secondUserMapper 查询:" + secondMapper.selectByPrimaryKey(roleKey));
}
- 日志
查询了两次。第二次查询并没有命中缓存。
2018-09-08 14:29:55.104 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.RoleMapper.selectByPrimaryKey:145 - ==> Preparing: SELECT ID,NAME,CODE,DESCRIPTION,created_time,updated_time FROM rOLE WHERE ID = ?
2018-09-08 14:29:55.125 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.RoleMapper.selectByPrimaryKey:145 - ==> Parameters: 1(Long)
2018-09-08 14:29:55.142 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.RoleMapper.selectByPrimaryKey:145 - <== Total: 1
firstUserMapper 查询:Role{id=1, name='管理员', code='admin', description='这个系统里天下第一', createdTime=Sat Sep 08 04:37:10 CST 2018, updatedTime=Sat Sep 08 04:37:10 CST 2018}
2018-09-08 14:29:55.145 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.RoleMapper:62 - Cache Hit Ratio [com.github.houbb.mybatis.learn.cache.mapper.RoleMapper]: 0.0
2018-09-08 14:29:55.145 DEBUG [main] org.springframework.jdbc.datasource.DataSourceUtils:114 - Fetching JDBC Connection from DataSource
2018-09-08 14:29:55.161 DEBUG [main] org.mybatis.spring.transaction.SpringManagedTransaction:89 - JDBC Connection [jdbc:mysql://127.0.0.1:13306/mybatis?useUnicode=true&characterEncoding=UTF-8&useOldAlias, UserName=root@172.17.0.1, MySQL Connector Java] will not be managed by Spring
2018-09-08 14:29:55.161 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.RoleMapper.selectByPrimaryKey:145 - ==> Preparing: SELECT ID,NAME,CODE,DESCRIPTION,created_time,updated_time FROM rOLE WHERE ID = ?
2018-09-08 14:29:55.161 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.RoleMapper.selectByPrimaryKey:145 - ==> Parameters: 1(Long)
2018-09-08 14:29:55.163 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.RoleMapper.selectByPrimaryKey:145 - <== Total: 1
secondUserMapper 查询:Role{id=1, name='管理员', code='admin', description='这个系统里天下第一', createdTime=Sat Sep 08 04:37:10 CST 2018, updatedTime=Sat Sep 08 04:37:10 CST 2018}
实验二
- 场景
两个 session,第一个提交事务
- 代码
@Test
public void firstCommitTest() {
final Long roleKey = 1L;
SqlSession firstSqlSession = sqlSessionFactory.openSession(true);
SqlSession secondSqlSession = sqlSessionFactory.openSession(true);
RoleMapper firstMapper = firstSqlSession.getMapper(RoleMapper.class);
RoleMapper secondMapper = secondSqlSession.getMapper(RoleMapper.class);
System.out.println("firstUserMapper 查询:" + firstMapper.selectByPrimaryKey(roleKey));
firstSqlSession.commit();
System.out.println("secondUserMapper 查询:" + secondMapper.selectByPrimaryKey(roleKey));
}
- 日志
命中缓存
2018-09-08 14:33:27.360 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.RoleMapper.selectByPrimaryKey:145 - ==> Preparing: SELECT ID,NAME,CODE,DESCRIPTION,created_time,updated_time FROM rOLE WHERE ID = ?
2018-09-08 14:33:27.380 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.RoleMapper.selectByPrimaryKey:145 - ==> Parameters: 1(Long)
2018-09-08 14:33:27.398 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.RoleMapper.selectByPrimaryKey:145 - <== Total: 1
firstUserMapper 查询:Role{id=1, name='管理员', code='admin', description='这个系统里天下第一', createdTime=Sat Sep 08 04:37:10 CST 2018, updatedTime=Sat Sep 08 04:37:10 CST 2018}
2018-09-08 14:33:27.416 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.RoleMapper:62 - Cache Hit Ratio [com.github.houbb.mybatis.learn.cache.mapper.RoleMapper]: 0.5
secondUserMapper 查询:Role{id=1, name='管理员', code='admin', description='这个系统里天下第一', createdTime=Sat Sep 08 04:37:10 CST 2018, updatedTime=Sat Sep 08 04:37:10 CST 2018}
实验三
不同 sql session 更新的影响。
- 场景
两个 session,第一个提交事务。第三个事务更新,第二个事务查询。
- 代码
@Test
public void firstCommitUpdateTest() {
final Long roleKey = 1L;
SqlSession firstSqlSession = sqlSessionFactory.openSession(true);
SqlSession secondSqlSession = sqlSessionFactory.openSession(true);
SqlSession thirdSqlSession = sqlSessionFactory.openSession(true);
RoleMapper firstMapper = firstSqlSession.getMapper(RoleMapper.class);
RoleMapper secondMapper = secondSqlSession.getMapper(RoleMapper.class);
RoleMapper thirdMapper = thirdSqlSession.getMapper(RoleMapper.class);
System.out.println("firstUserMapper 查询:" + firstMapper.selectByPrimaryKey(roleKey));
firstSqlSession.commit();
Role role = new Role();
role.setId(roleKey);
role.setDescription("新的描述哈哈哈");
thirdMapper.updateByPrimaryKeySelective(role);
thirdSqlSession.commit();
System.out.println("secondUserMapper 查询:" + secondMapper.selectByPrimaryKey(roleKey));
}
- 日志
执行更新操作之后,需要再次查询数据库。
2018-09-08 14:40:48.712 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.RoleMapper.selectByPrimaryKey:145 - ==> Preparing: SELECT ID,NAME,CODE,DESCRIPTION,created_time,updated_time FROM rOLE WHERE ID = ?
2018-09-08 14:40:48.734 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.RoleMapper.selectByPrimaryKey:145 - ==> Parameters: 1(Long)
2018-09-08 14:40:48.750 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.RoleMapper.selectByPrimaryKey:145 - <== Total: 1
firstUserMapper 查询:Role{id=1, name='管理员', code='admin', description='新的描述哈哈哈', createdTime=Sat Sep 08 04:37:10 CST 2018, updatedTime=Sat Sep 08 06:39:51 CST 2018}
2018-09-08 14:40:48.785 DEBUG [main] org.springframework.jdbc.datasource.DataSourceUtils:114 - Fetching JDBC Connection from DataSource
2018-09-08 14:40:48.795 DEBUG [main] org.mybatis.spring.transaction.SpringManagedTransaction:89 - JDBC Connection [jdbc:mysql://127.0.0.1:13306/mybatis?useUnicode=true&characterEncoding=UTF-8&useOldAlias, UserName=root@172.17.0.1, MySQL Connector Java] will not be managed by Spring
2018-09-08 14:40:48.795 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.RoleMapper.updateByPrimaryKeySelective:145 - ==> Preparing: UPDATE rOLE SET DESCRIPTION = ? WHERE ID = ?
2018-09-08 14:40:48.796 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.RoleMapper.updateByPrimaryKeySelective:145 - ==> Parameters: 新的描述哈哈哈(String), 1(Long)
2018-09-08 14:40:48.797 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.RoleMapper.updateByPrimaryKeySelective:145 - <== Updates: 1
2018-09-08 14:40:48.798 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.RoleMapper:62 - Cache Hit Ratio [com.github.houbb.mybatis.learn.cache.mapper.RoleMapper]: 0.0
2018-09-08 14:40:48.798 DEBUG [main] org.springframework.jdbc.datasource.DataSourceUtils:114 - Fetching JDBC Connection from DataSource
2018-09-08 14:40:48.806 DEBUG [main] org.mybatis.spring.transaction.SpringManagedTransaction:89 - JDBC Connection [jdbc:mysql://127.0.0.1:13306/mybatis?useUnicode=true&characterEncoding=UTF-8&useOldAlias, UserName=root@172.17.0.1, MySQL Connector Java] will not be managed by Spring
2018-09-08 14:40:48.807 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.RoleMapper.selectByPrimaryKey:145 - ==> Preparing: SELECT ID,NAME,CODE,DESCRIPTION,created_time,updated_time FROM rOLE WHERE ID = ?
2018-09-08 14:40:48.807 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.RoleMapper.selectByPrimaryKey:145 - ==> Parameters: 1(Long)
2018-09-08 14:40:48.808 DEBUG [main] com.github.houbb.mybatis.learn.cache.mapper.RoleMapper.selectByPrimaryKey:145 - <== Total: 1
secondUserMapper 查询:Role{id=1, name='管理员', code='admin', description='新的描述哈哈哈', createdTime=Sat Sep 08 04:37:10 CST 2018, updatedTime=Sat Sep 08 06:39:51 CST 2018}
备注
mybatis 缓存不适应于多表关联的场景。
可以再一个表的缓存中,引用另一个关联表的 cache 即可。
参考资料
- mybatis