相关代码请查阅:mybatisCacheDemo
问题详述:
Mybatis同一个事务中,执行相同SQL,第一条Sql执行,第二条不执行,原因:mybatis对查询的语句会存在一级缓存中,如果在一个事务中,mybatis对同一个session多次查询同一个sql语句就会去找缓存而不是再去查一次数据库
一级缓存
代码示例
默认情况下,mybatis开启并使用了一级缓存。
示例一
/**
* 开启事务,测试一级缓存效果
**/
@ApiOperation("获取指定id的学生信息")
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
@ResponseBody
@Transactional(rollbackFor = Throwable.class)
public CommonResult<Student> selectById(@PathVariable("id") Long id) {
Student student = new Student();
//第一次查询,缓存到一级缓存
student = studentService.selectById(id);
System.out.println(student.toString());
//第二次查询,直接读取一级缓存
student = studentService.selectById(id);
System.out.println(student.toString());
return CommonResult.success(student);
}
执行结果:
2021-11-05 10:23:30.230 DEBUG 21732 --- [nio-8080-exec-1] c.s.r.m.m.S.selectByPrimaryKey : ==> Preparing: select id, name, gender, age from student where id = ?
2021-11-05 10:23:30.245 DEBUG 21732 --- [nio-8080-exec-1] c.s.r.m.m.S.selectByPrimaryKey : ==> Parameters: 1(Long)
2021-11-05 10:23:30.261 DEBUG 21732 --- [nio-8080-exec-1] c.s.r.m.m.S.selectByPrimaryKey : <== Total: 1
Student [Hash = 52726004, id=1, name=Jack, gender=male, age=24, serialVersionUID=1]
Student [Hash = 52726004, id=1, name=Jack, gender=male, age=24, serialVersionUID=1]
我们可以看到,只有第一次真正查询了数据库,后续的查询使用了一级缓存。
由于使用数据库连接池,默认每次查询完之后自动commit,这将导致两次查询使用的不是同一个SqlSession,根据一级缓存的原理,它将不会生效。当我们开启事务,使用@Transactional 的注解(对 mybatis 来说是在同一个 SESSION 中),两次查询都在同一个sqlSession,从而让第二次查询命中一级缓存。
一级缓存配置有两种级别,一种是session,另一种是statement,默认是session级别,即在同一个Mybatis会话中执行的所有语句,都会共享这一个 缓存。statement级别,可以理解为缓存只对当前执行的这一个statement的有效(即仅针对一次查询)。
示例二
验证在一次Sqlsession中,如果对数据库发生了修改操作,一级缓存是否会失效
/**
* 开启事务,测试一级缓存效果
**/
@ApiOperation("查询并新增学生")
@RequestMapping(value = "/selectByIdAndAddStudent/{id}", method = RequestMethod.GET)
@ResponseBody
@Transactional(rollbackFor = Throwable.class)
public CommonResult<Student> selectByIdAndAddStudent(@PathVariable("id") Long id) {
System.out.println(studentService.selectById(id));
System.out.println("增加了" + studentService.insert(buildStudent()) + "个学生");
System.out.println(studentService.selectById(id));
return CommonResult.success(null);
}
执行结果:
2021-11-05 11:18:01.728 DEBUG 33940 --- [nio-8080-exec-7] c.s.r.m.m.S.selectByPrimaryKey : ==> Preparing: select id, name, gender, age from student where id = ?
2021-11-05 11:18:01.743 DEBUG 33940 --- [nio-8080-exec-7] c.s.r.m.m.S.selectByPrimaryKey : ==> Parameters: 1(Long)
2021-11-05 11:18:01.761 DEBUG 33940 --- [nio-8080-exec-7] c.s.r.m.m.S.selectByPrimaryKey : <== Total: 1
Student [Hash = 1090685328, id=1, name=Jack, gender=male, age=24, serialVersionUID=1]
2021-11-05 11:18:01.771 DEBUG 33940 --- [nio-8080-exec-7] c.s.r.m.m.StudentMapper.insertSelective : ==> Preparing: insert into student ( name, gender, age ) values ( ?, ?, ? )
2021-11-05 11:18:01.773 DEBUG 33940 --- [nio-8080-exec-7] c.s.r.m.m.StudentMapper.insertSelective : ==> Parameters: 明明(String), male(String), 20(Integer)
2021-11-05 11:18:01.774 DEBUG 33940 --- [nio-8080-exec-7] c.s.r.m.m.StudentMapper.insertSelective : <== Updates: 1
2021-11-05 11:18:01.774 DEBUG 33940 --- [nio-8080-exec-7] c.s.r.m.m.S.insertSelective!selectKey : ==> Preparing: SELECT LAST_INSERT_ID()
2021-11-05 11:18:01.774 DEBUG 33940 --- [nio-8080-exec-7] c.s.r.m.m.S.insertSelective!selectKey : ==> Parameters:
2021-11-05 11:18:01.776 DEBUG 33940 --- [nio-8080-exec-7] c.s.r.m.m.S.insertSelective!selectKey : <== Total: 1
增加了1个学生
2021-11-05 11:18:01.776 DEBUG 33940 --- [nio-8080-exec-7] c.s.r.m.m.S.selectByPrimaryKey : ==> Preparing: select id, name, gender, age from student where id = ?
2021-11-05 11:18:01.776 DEBUG 33940 --- [nio-8080-exec-7] c.s.r.m.m.S.selectByPrimaryKey : ==> Parameters: 1(Long)
2021-11-05 11:18:01.777 DEBUG 33940 --- [nio-8080-exec-7] c.s.r.m.m.S.selectByPrimaryKey : <== Total: 1
Student [Hash = 1084879628, id=1, name=Jack, gender=male, age=24, serialVersionUID=1]
我们可以看到,在修改操作后执行的相同查询,查询了数据库,「一级缓存失效」。
二级缓存
在上述提到的一级缓存的中,其最大的共享范围就是一个SqlSession内部,而想要在多个SqlSession中进行共享缓存,则需要用到二级缓存。
1、在Mybatis的配置文件或application.yml中开启二级缓存。为避免一级缓存干扰,将一级缓存设置为statement级别。
# 开启二级缓存
mybatis:
configuration:
local-cache-scope: statement
cache-enabled: true
1、在Mybatis的映射文件中配置cache
cache标签用于声明这个namespace使用二级缓存,并且可以自定义配置。
<cache/>
代码示例:
示例一
/**
* 测试二级缓存效果
**/
@ApiOperation("获取指定id的学生信息")
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
@ResponseBody
// @Transactional(rollbackFor = Throwable.class)
public CommonResult<Student> selectById(@PathVariable("id") Long id) {
Student student = new Student();
student = studentService.selectById(id);
System.out.println(student.toString());
student = studentService.selectById(id);
System.out.println(student.toString());
return CommonResult.success(student);
}
执行结果:
2021-11-05 13:56:46.765 DEBUG 23532 --- [nio-8080-exec-7] com.shaq.redis.mbg.mapper.StudentMapper : Cache Hit Ratio [com.shaq.redis.mbg.mapper.StudentMapper]: 0.0
2021-11-05 13:56:46.770 DEBUG 23532 --- [nio-8080-exec-7] c.s.r.m.m.S.selectByPrimaryKey : ==> Preparing: select id, name, gender, age from student where id = ?
2021-11-05 13:56:46.785 DEBUG 23532 --- [nio-8080-exec-7] c.s.r.m.m.S.selectByPrimaryKey : ==> Parameters: 1(Long)
2021-11-05 13:56:46.803 DEBUG 23532 --- [nio-8080-exec-7] c.s.r.m.m.S.selectByPrimaryKey : <== Total: 1
Student [Hash = 425057329, id=1, name=Jack, gender=male, age=24, serialVersionUID=1]
2021-11-05 13:56:46.806 DEBUG 23532 --- [nio-8080-exec-7] com.shaq.redis.mbg.mapper.StudentMapper : Cache Hit Ratio [com.shaq.redis.mbg.mapper.StudentMapper]: 0.5
Student [Hash = 1519320603, id=1, name=Jack, gender=male, age=24, serialVersionUID=1]
从上可知,第二次查询命中缓存,命中率是0.5。
示例二
验证如果对数据库发生了修改操作,是否会刷新该namespace下的二级缓存。
/**
* 测试二级缓存效果
**/
@ApiOperation("查询并新增学生")
@RequestMapping(value = "/selectByIdAndAddStudent/{id}", method = RequestMethod.GET)
@ResponseBody
// @Transactional(rollbackFor = Throwable.class)
public CommonResult<Student> selectByIdAndAddStudent(@PathVariable("id") Long id) {
System.out.println(studentService.selectById(id));
System.out.println("增加了" + studentService.insert(buildStudent()) + "个学生");
System.out.println(studentService.selectById(id));
return CommonResult.success(null);
}
执行结果:
2021-11-05 14:11:44.983 DEBUG 16080 --- [nio-8080-exec-1] com.shaq.redis.mbg.mapper.StudentMapper : Cache Hit Ratio [com.shaq.redis.mbg.mapper.StudentMapper]: 0.0
2021-11-05 14:11:44.992 DEBUG 16080 --- [nio-8080-exec-1] c.s.r.m.m.S.selectByPrimaryKey : ==> Preparing: select id, name, gender, age from student where id = ?
2021-11-05 14:11:45.006 DEBUG 16080 --- [nio-8080-exec-1] c.s.r.m.m.S.selectByPrimaryKey : ==> Parameters: 1(Long)
2021-11-05 14:11:45.022 DEBUG 16080 --- [nio-8080-exec-1] c.s.r.m.m.S.selectByPrimaryKey : <== Total: 1
Student [Hash = 1788139735, id=1, name=Jack, gender=male, age=24, serialVersionUID=1]
2021-11-05 14:11:45.033 DEBUG 16080 --- [nio-8080-exec-1] c.s.r.m.m.StudentMapper.insertSelective : ==> Preparing: insert into student ( name, gender, age ) values ( ?, ?, ? )
2021-11-05 14:11:45.034 DEBUG 16080 --- [nio-8080-exec-1] c.s.r.m.m.StudentMapper.insertSelective : ==> Parameters: 明明(String), male(String), 20(Integer)
2021-11-05 14:11:45.047 DEBUG 16080 --- [nio-8080-exec-1] c.s.r.m.m.StudentMapper.insertSelective : <== Updates: 1
2021-11-05 14:11:45.048 DEBUG 16080 --- [nio-8080-exec-1] c.s.r.m.m.S.insertSelective!selectKey : ==> Preparing: SELECT LAST_INSERT_ID()
2021-11-05 14:11:45.048 DEBUG 16080 --- [nio-8080-exec-1] c.s.r.m.m.S.insertSelective!selectKey : ==> Parameters:
2021-11-05 14:11:45.049 DEBUG 16080 --- [nio-8080-exec-1] c.s.r.m.m.S.insertSelective!selectKey : <== Total: 1
增加了1个学生
2021-11-05 14:11:45.049 DEBUG 16080 --- [nio-8080-exec-1] com.shaq.redis.mbg.mapper.StudentMapper : Cache Hit Ratio [com.shaq.redis.mbg.mapper.StudentMapper]: 0.0
2021-11-05 14:11:45.049 DEBUG 16080 --- [nio-8080-exec-1] c.s.r.m.m.S.selectByPrimaryKey : ==> Preparing: select id, name, gender, age from student where id = ?
2021-11-05 14:11:45.050 DEBUG 16080 --- [nio-8080-exec-1] c.s.r.m.m.S.selectByPrimaryKey : ==> Parameters: 1(Long)
2021-11-05 14:11:45.050 DEBUG 16080 --- [nio-8080-exec-1] c.s.r.m.m.S.selectByPrimaryKey : <== Total: 1
Student [Hash = 685337146, id=1, name=Jack, gender=male, age=24, serialVersionUID=1]
我们可以看到,在修改操作后执行的相同查询,查询了数据库,没有走cache。
总结:
Mybatis默认的session级别一级缓存,一级缓存最大共享范围仅限同一个SqlSession,在多个Sqlsession或者分布式的环境中,数据库的写操作容易引起脏数据。一级缓存内存设计简单,只是一个没有容量限定的HashMap。二级缓存相对于一级缓存,实现了Sqlsession间的共享,同时粒度更细,能够到namespace级别。
「参考」聊聊MyBatis缓存机制
原文始发于微信公众号(写代码的ZaytonSquid):教你如何看懂Mybatis的缓存问题
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/246389.html