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缓存机制

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MyBatis 缓存是一个常见的面试题。下面是一些常见的 MyBatis 缓存面试问题和答案: 1. 什么是 MyBatis 缓存MyBatis 缓存是一个用于提高系统性能的机制,它可以在查询数据库时缓存结果,并在后续的相同查询中直接返回缓存的结果,减少数据库访问次数。 2. MyBatis 缓存有哪些类型? MyBatis 缓存有两种类型:一级缓存(本地缓存)和二级缓存(全局缓存)。 3. 什么是一级缓存?如何开启和关闭一级缓存? 一级缓存MyBatis 默认开启的,它是在 SqlSession 的生命周期内有效的,可以通过配置文件或编程方式关闭一级缓存。 4. 什么是二级缓存?如何开启和关闭二级缓存? 二级缓存是全局共享的缓存,可以被多个 SqlSession 共享。要开启二级缓存,需要在 MyBatis 配置文件中进行相应的配置。关闭二级缓存也是在配置文件中设置。 5. MyBatis 的二级缓存如何实现缓存更新和失效? MyBatis 的二级缓存使用了基于触发器的机制来实现缓存更新和失效。当数据发生变化时,触发相应的更新操作,保证缓存数据的一致性。 6. MyBatis 缓存的实现原理是什么? MyBatis 缓存的实现原理是通过将查询结果缓存在内存中,使用一个 Map 结构来存储查询结果。当需要查询时,首先检查缓存是否存在对应的结果,如果存在则直接返回缓存结果,否则执行数据库查询操作,并将结果放入缓存。 这些问题涵盖了 MyBatis 缓存的基本概念和实现原理。希望对你有所帮助!如果你还有其他问题,请继续提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值