专栏名称: Java基基
一个苦练基本功的 Java 公众号,所以取名 Java 基基
目录
相关文章推荐
吉林省高级人民法院  ·  代表委员看法院丨走进吉林,司法再奏新乐章 ·  昨天  
吉林省高级人民法院  ·  代表委员看法院丨走进吉林,司法再奏新乐章 ·  昨天  
金错刀  ·  百亿导演饺子,背后有个“抠门”高人 ·  2 天前  
河南省消费者协会  ·  当心资金被盗!中国银联严正声明! ·  2 天前  
河南省消费者协会  ·  当心资金被盗!中国银联严正声明! ·  2 天前  
51好读  ›  专栏  ›  Java基基

美团二面:为什么不推荐使用 MyBatis 二级缓存?大部分人都答不上来!

Java基基  · 公众号  ·  · 2024-09-28 17:17

正文

👉 这是一个或许对你有用 的社群

🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入 芋道快速开发平台 知识星球。 下面是星球提供的部分资料:

👉 这是一个或许对你有用的开源项目

国产 Star 破 10w+ 的开源项目,前端包括管理后台 + 微信小程序,后端支持单体和微服务架构。

功能涵盖 RBAC 权限、SaaS 多租户、数据权限、商城、支付、工作流、大屏报表、微信公众号等等功能:

  • Boot 仓库:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • Cloud 仓库:https://gitee.com/zhijiantianya/yudao-cloud
  • 视频教程:https://doc.iocoder.cn
【国内首批】支持 JDK 21 + SpringBoot 3.2.2、JDK 8 + Spring Boot 2.7.18 双版本

来源:blog.csdn.net/xujingyiss
/article/details/123481116


为了增加查询的性能,mybatis 提供了二级缓存架构,分为一级缓存和二级缓存。

这两级缓存最大的区别就是:一级缓存是会话级别的,只要出了这个 SqlSession,缓存就没用了。而二级缓存可以跨会话,多个会话可以使用相同的缓存!

一级缓存使用简单,默认就开启。二级缓存需要手动开启,相对复杂,而且要注意的事项也多,否则可能有隐患。

一级缓存

应用场景

订单表与会员表是存在一对多的关系,为了尽可能减少join查询,进行了分阶段查询。即先查询出订单表,再根据 member_id 字段查询出会员表,最后进行数据整合。而如果订单表中存在重复的 member_id ,就会出现很多重复查询。

针对这种情况,mybatis通过一级缓存来解决:在同一次查询会话(SqlSession)中如果出现相同的语句及参数,就会从缓存中取出,不再走数据库查询。

一级缓存只能作用于查询会话中,所以也叫做会话缓存。

生效的条件

一级缓存要生效,必须满足以下条件条件:

  • 必须是相同的会话
  • 必须是同一个 mapper,即同一个 namespace
  • 必须是相同的 statement,即同一个 mapper 中的同一个方法
  • 必须是相同的 sql 和参数
  • 查询语句中间没有执行 session.clearCache() 方法
  • 查询语句中间没有执行 insert/update/delete 方法(无论变动记录是否与缓存数据有无关系)

与springboot集成时一级缓存不生效原因

因为一级缓存是会话级别的,要生效的话,必须要在同一个 SqlSession 中。但是与 springboot 集成的 mybatis,默认每次执行sql语句时,都会创建一个新的 SqlSession!所以一级缓存才没有生效。

当调用 mapper 的方法时,最终会执行到 SqlSessionUtils getSqlSession 方法,在这个方法中会尝试在事务管理器中获取 SqlSession,如果没有开启事务,那么就会 new 一个 DefaultSqlSession

所以说,即便在同一个方法中,通过同一个 mapper 连续调用两次相同的查询方法,也不会触发一级缓存。

解决与springboot集成时一级缓存不生效问题

在上面的代码中也看到了,mybatis 在查询时,会先从事务管理器中尝试获取 SqlSession ,取不到才会去创建新的 SqlSession 。所以可以猜测只要将方法开启事务,那么一级缓存就会生效。

加上 @Transactional 注解,看下效果:

没错,的确生效了。在代码中可以看到,从事务管理器中,获取到了 SqlSession:

再看看源码中是什么时候将 SqlSession 设置到事务管理器中的。

SqlSessionUtils 中,在获取到 SqlSession 后,会调用 registerSessionHolder 方法注册 SessionHolder 到事务管理器:

具体是在 TransactionSynchronizationManager bindResource 方法中操作的,将 SessionHolder 保存到线程本地变量 (ThreadLocal) resources 中,这是每个线程独享的。

然后在下次查询时,就可以从这里取出此 SqlSession,使用同一个 SqlSession 查询,一级缓存就生效了。

所以基本原理就是:如果当前线程存在事物,并且存在相关会话,就从 ThreadLocal 中取出。如果没有事务,就重新创建一个 SqlSession 并存储到 ThreadLocal 当中,共下次查询使用。

至于缓存查询数据的地方,是在 BaseExecutor 中的 queryFromDatabase 方法中。执行 doQuery 从数据库中查询数据后,会立马缓存到 localCache(PerpetualCache类型) 中:

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

二级缓存

应用场景

业务系统中存在很多的静态数据如,字典表、菜单表、权限表等,这些数据的特性是不会轻易修改但又是查询的热点数据。

一级缓存针对的是同一个会话当中相同SQL,并不适合这情热点数据的缓存场景。

为了解决这个问题引入了二级缓存,它脱离于会话之外,多个会话可以使用相同的缓存。

看一个例子:

@RestController
@RequestMapping("item")
public class ItemController {
 
    @Autowired
    private ItemMapper itemMapper;
 
    @GetMapping("/{id}")
    public void getById(@PathVariable("id") Long id) {
        System.out.println("==================== begin ====================");
        Item item = itemMapper.selectById(id);
        System.out.println(JSON.toJSONString(item));
    }
 

当发送两次 get 请求时(两个不同的会话),通过日志可以发现第二次查询使用的是缓存

开启的方法

二级缓存需要手动来开启,mybatis 默认没有开启二级缓存。

1)在 yaml 中配置 cache-enabled 为 true

mybatis-plus:
  configuration:
    cache-enabled: true

2)Mapper 接口上添加 @CacheNamespace 注解

3)实体类实现 Serializable 接口

生效的条件

  • 当会话提交或关闭之后才会填充二级缓存
  • 必须是同一个 mapper,即同一个命名空间
  • 必须是相同的 statement,即同一个 mapper 中的同一个方法
  • 必须是相同的 SQL 语句和参数
  • 如果 readWrite=true (默认就是true),实体对像必须实现 Serializable 接口

缓存清除条件

  • 只有修改会话提交之后,才会执行清空操作
  • xml 中配置的 update 不能清空 @CacheNamespace 中的缓存数据
  • 任何一种增删改操作都会清空整个 namespace 中的缓存

源码中是如何填充二级缓存的?

在生效条件中提到了,二级缓存必须要在会话提交或关闭之后,才能生效!

在查询到结果后,会调用 SqlSession 的 commit 方法进行提交(如果开启事务的话,提交 SqlSession 走的不是这里了,但最终填充二级缓存的地方是一样的。):

在此方法中,最终会调用到 TransactionalCache flushPendingEntries 方法中填充二级缓存:

springboot 集成 mybatis 的话,如果没有开启事务,每次执行查询,都会创建新的 SqlSession,所以即使是在同一个方法中进行查询操作,那也是跨会话的。

查询时如何使用二级缓存?

在查询的时候,最终会调用 MybatisCachingExecutor 的 query 方法,里面会从 TransactionalCacheManager 中尝试根据 key 获取二级缓存的内容。

可以看到,这个 key 很长,由 mapper、调用的查询方法、SQL 等信息拼接而成,这也是为什么想要二级缓存生效,必须满足前面所说的条件。

如果能在二级缓存中查询到,就直接返回了,不需要访问数据库。

具体的调用层数实在太多,用到了装饰者模式,最终是在 PerpetualCache 中获取缓存的:

打印日志是在 LoggingCache 中:







请到「今天看啥」查看全文