教你如何看懂Mybatis的缓存问题

相关代码请查阅: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

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!