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

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

时间:2024-04-12 10:22:39浏览次数:32  
标签:缓存 二面 美团 SqlSession 查询 item 二级缓存 MyBatis id

为了增加查询的性能,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 集成时一级缓存不生效问题

Spring Boot 基础就不介绍了,推荐看这个实战项目:

https://github.com/javastacks/spring-boot-best-practice

在上面的代码中也看到了,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类型) 中:

二级缓存

应用场景

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

一级缓存针对的是同一个会话当中相同 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 中:

为什么 MyBatis 默认不开启二级缓存?

答案就是,不推荐使用二级缓存!

二级缓存虽然能带来一定的好处,但是有很大的隐藏危害!

它的缓存是以 namespace(mapper) 为单位的,不同 namespace 下的操作互不影响。且 insert/update/delete 操作会清空所在 namespace 下的全部缓存。

那么问题就出来了,假设现在有 ItemMapper 以及 XxxMapper,在 XxxMapper 中做了表关联查询,且做了二级缓存。此时在 ItemMapper 中将 item 信息给删了,由于不同 namespace 下的操作互不影响,XxxMapper 的二级缓存不会变,那之后再次通过 XxxMapper 查询的数据就不对了,非常危险。

来看一个例子:

@Mapper
@Repository
@CacheNamespace
public interface XxxMapper {

    @Select("select i.id itemId,i.name itemName,p.amount,p.unit_price unitPrice " +
            "from item i JOIN payment p on i.id = p.item_id where i.id = #{id}")
    List<PaymentVO> getPaymentVO(Long id);

}

@Autowired
private XxxMapper xxxMapper;

@Test
void test() {
	System.out.println("==================== 查询PaymentVO ====================");
	List<PaymentVO> voList = xxxMapper.getPaymentVO(1L);
	System.out.println(JSON.toJSONString(voList.get(0)));
	System.out.println("====================  更新item表的name ==================== ");
	Item item = itemMapper.selectById(1);
	item.setName("java并发编程");
	itemMapper.updateById(item);
	System.out.println("====================  重新查询PaymentVO ==================== ");
	List<PaymentVO> voList2 = xxxMapper.getPaymentVO(1L);
	System.out.println(JSON.toJSONString(voList2.get(0)));
}

上面的代码,test() 方法中前后两次调用了 xxxMapper.getPaymentVO 方法,因为没有加 @Transactional 注解,所以前后两次查询,是两个不同的会话,第一次查询完后,SqlSession 会自动 commit,所以二级缓存能够生效;

然后在中间进行了 Item 表的更新操作,修改了下名称;

由于 itemMapper 与 xxxMapper 不是同一个命名空间,所以 itemMapper 执行的更新操作不会影响到 xxxMapper 的二级缓存;

再次调用 xxxMapper.getPaymentVO,发现取出的值是走缓存的,itemName 还是老的。但实际上 itemName 在上面已经被改了!

执行日志如下:

所以说,二级缓存的隐藏危害是比较大的,当有表关联时,一个不注意就会出问题,不建议使用。

版权声明:本文为CSDN博主「xujingyiss」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/xujingyiss/article/details/123481116

更多文章推荐:

1.Spring Boot 3.x 教程,太全了!

2.2,000+ 道 Java面试题及答案整理(2024最新版)

3.免费获取 IDEA 激活码的 7 种方式(2024最新版)

觉得不错,别忘了随手点赞+转发哦!

标签:缓存,二面,美团,SqlSession,查询,item,二级缓存,MyBatis,id
From: https://www.cnblogs.com/javastack/p/18130622

相关文章

  • 实况窗助力美团打造鸿蒙原生外卖新体验,用户可实时掌握外卖进展
    自2023年华为宣布全新HarmonyOS NEXT蓄势待发,鸿蒙原生应用全面启动以来,已有金融、旅行、社交等多个领域的企业和开发者陆续宣布加入鸿蒙生态。其中,美团作为国内头部的科技零售企业,是首批加入鸿蒙生态的伙伴,其下的美团外卖App基于HarmonyOS SDK高效展开了鸿蒙原生应用的开发,仅用6......
  • 25.再次整理mybatis坑
    前面就没有整理好mybatis也是因为跳过这个直接去学的springboot也导致没有更深层次理解也是逢坑踩坑一下我maven版本为2.2.5.RELEASE1导入mybatis和mysql驱动依赖如果mybatis加载不出来降低版本参考:https://blog.csdn.net/S852509769/article/details/134456125<!-......
  • SSM整合(Spring、SpringMVC、Mybatis)
    第一步:新建一个Webx项目第二步:导入相关依赖包第三步:配置Spring核心配置文件第四步:配置SpringMVC核心配置文件第五步:配置web.xml文件......
  • 实况窗助力美团打造鸿蒙原生外卖新体验,用户可实时掌握外卖进展
    自2023年华为宣布全新HarmonyOSNEXT蓄势待发,鸿蒙原生应用全面启动以来,已有金融、旅行、社交等多个领域的企业和开发者陆续宣布加入鸿蒙生态。其中,美团作为国内头部的科技零售企业,是首批加入鸿蒙生态的伙伴,其下的美团外卖App基于HarmonyOSSDK高效展开了鸿蒙原生应用的开发,仅用6周......
  • 使用Mybatis执行sql脚本
    pom.xml<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://ma......
  • 美团一面,面试官让介绍AQS原理并手写一个同步器,直接凉了
    写在开头今天在牛客上看到了一个帖子,一个网友吐槽美团一面上来就让手撕同步器,没整出来,结果面试直接凉凉。就此联想到一周前写的一篇关于AQS知识点解析的博文,当时也曾埋下伏笔说后面会根据AQS的原理实现一个自定义的同步器,那今天就来把这个坑给填上哈。常用的AQS架构同步器类......
  • 全新一款基于mybatis的ORM框架,你没有见过的好用的框架,不信的前来看看!!!
    喜欢的朋友加入QQ群:917404304,群里不仅可以提mybatis-mp框架问题,还可以帮你解决后端的各种问题!另外,喜欢的朋友,帮忙关注和star(点点小爱心)!https://gitee.com/mybatis-mp特别申明:禁止在非法项目中使用,否则后果自负!与众不同的几大亮点:1:mybatis-mp-亮点一:可自定义动......
  • mybatis-mp:这是一款你没见过的好用的ORM框架,值得你拥有!不信,可以试试看!
    https://gitee.com/mybatis-mp @Data@ResultEntity(SysRole.class)publicclassOneToManyVo{privateIntegerid;privateStringname;privateLocalDateTimecreateTime;@ResultField(value="otherName")privateStringasNam......
  • 小美的数组构造(美团2024届秋招笔试第二场编程真题)
    题面核心思想dp[i][j]表示前i个数字和为j时的组合数那么第i个数的取法有1<=k<=j需要遍历第i个数取k前i-1个数取j-k时dp[i][j]=(dp[i][j]+dp[i-1][j-k])%MOD;注意是和为j第i个数取k所以是dp[i][j]。同时需要判断第i个数不能和a数组取相同的......
  • Mybatis入门
    1.Mybatis的入门1.1导包1.2准备核心配置文件MyBatis-Config.xml点击查看代码<configuration><!--引入配置文件信息,这里不能加classpath:。 resource:引入类路径下的资源,即classpath,所以不需要写classpath: url:引入网络路径或磁盘路径下的资源 --> <properti......