Mybatis-Mybatis缓存机制&Mybatis动态代理机制

本文深入解析MyBatis的一级与二级缓存机制,包括缓存的生命周期、内部设计、脏数据问题及解决方案。同时,介绍了如何配置二级缓存以提高性能,并探讨了在多表查询和分布式环境下的应用局限。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Mybatis缓存机制:

我们一般都会使用mybatis的默认缓存配置,但是mybatis的缓存机制有不足之处,使用中可能会造成脏数据问题。

Mybatis一级缓存:

mybatis一级缓存是sqlsession级别的。一级缓存的作用域是一个sqlsession。mbatis默认开启一级缓存
在同一个sqlsession中,执行相同的查询sql。第一次会去查询数据库,并写道缓存中,第二次直接从缓存中读取。若对应数据发生增删改操作,则缓存失效。
在这里插入图片描述
一级缓存是以sqlsession为单位进行划分的,若找不到在去数据库查询,然后将结果写到缓存中。mybatis内部使用HashMap ,key为hashcode+statementid+sql语句。value为查询出来的结果集映射成的java对象。

一级缓存带来的脏数据问题:

当使用两个或者两个以上的sqlsession时,由于缓存数据是sqlsession内共享的,当另一个sqlsession进行增删改操作时,其它sqlsession时感知不到,因此其此时缓存中的数据编程脏数据。

@Test
public void testLocalCacheScope() throws Exception {
        SqlSession sqlSession1 = factory.openSession(true); 
        SqlSession sqlSession2 = factory.openSession(true); 
 
        StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
        StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
 
        System.out.println("studentMapper读取数据: " + studentMapper.getStudentById(1));
        System.out.println("studentMapper读取数据: " + studentMapper.getStudentById(1));
        System.out.println("studentMapper2更新了" + studentMapper2.updateStudentName("小岑",1) + "个学生的数据");
        System.out.println("studentMapper读取数据: " + studentMapper.getStudentById(1));
        System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1));
}

在这里插入图片描述

一级缓存总结:
  1. mybatis一级缓存声明周期和sqlsession一致。
  2. mybatis缓存内部设计简单,只有一个没有容量限制的hashmap,缓存性能有欠缺/
  3. mybatis一级缓存的最大范围是sqlsession内部,在多个sqlsession或者分布式环境下,数据库写操作会引起脏数据问题
Mybatis二级缓存:

mybatis二级缓存指mapper映射文件。二级缓存的作用域是同一个namespace(命名空间)下的mapper映射文件内容。多个sqlsession共享。
Mybatis需要手动设置二级缓存
同一个namespace下的mapper文件中,执行相同的sql查询语句,第一次会读取数据库,并写道缓存中,若中间没有进行增删改操作,则从缓存读取查询结果。
在这里插入图片描述

配置二级缓存:
  • 在全局配置文件中进行如下配置
<settings>
    <!-- 打开延迟加载的开关 -->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!-- 将积极加载改为消极加载,即延迟加载 -->
    <setting name="aggressiveLazyLoading" value="false"/>
     <!--开启二级缓存-->
     <setting name="cacheEnabled" value="true"/>
</settings>

  • 在对应mapper下进行如下配置:
  1. cache可配置参数
type:cache使用的类型,默认是PerpetualCache,这在一级缓存中提到过。
 
eviction: 定义回收的策略,常见的有FIFO,LRU。
 
flushInterval: 配置一定时间自动刷新缓存,单位是毫秒。
 
size: 最多缓存对象的个数。
 
readOnly: 是否只读,若配置可读写,则需要对应的实体类能够序列化。
 
blocking: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。
  1. 映射的java对象实现序列化

  2. 映射文件对应的需要开启二级缓存的namespace进行如下设置

<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"></cache>

<!--cache-ref 代表引用别的namespace中的Cache配置,两个namespace使用同一个Cache-->
<cache-ref namespace="mapper.StudentMapper"/>
 
 //一二级缓存均有用
select 标签下:user  userCache标签 是否使用缓存
update/selete/update 标签下  : flushCache标签  刷新缓存
注意: MyBatis的二级缓存不适应用于映射文件中存在多表查询的情况。

通常我们会为每个单表创建单独的映射文件,由于MyBatis的二级缓存是基于namespace的,多表查询语句所在的namspace无法感应到其他namespace中的语句对多表查询中涉及的表进行的修改,引发脏数据问题。
可以使用Cache-ref,这样两个映射文件对应的Sql操作都使用的是同一块缓存了。
不过这样做的后果是,缓存的粒度变粗了,多个Mapper namespace下的所有操作都会对缓存使用造成影响。

二级缓存总结:
  1. 二级缓存相对于一级缓存来说,实现了sqlsession间的缓存数据共享,粒度到namespace级别,
  2. mybatis在进行多表查询时,极大可能会出现脏数据,有设计上缺陷,安全使用二级缓存的条件比较苛刻。
  3. 在分布式环境下,由于默认的mybatis cache都是基于本地的,分布式环境下必然会出现脏数据问题(多个server服务器都会进行缓存,且互不通知数据更新),需要使用集中式缓存将mybatis的cache接口实现,有一定的开发成本。相比较下,直接使用redis等分布式缓存可能成本更低,安全性更高。
建议在生产环境下关闭mybatis缓存,只作为一个ORM(对象关系映射)框架

Mybatis动态代理机制:

参考博客:

美团技术聊聊MyBatis缓存机制

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值