Mybatis的一级缓存、二级缓存

本文深入探讨Mybatis的一级缓存与二级缓存机制,包括缓存的开启方式、有效范围、数据清理条件及缓存策略。通过具体示例说明如何在不同场景下利用缓存提高数据库查询效率。

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

查询缓存

  • 在开启了log4j日记的基础上,可以更加清楚地观察到Mybatis一级缓存以及二级缓存的过程。所以在了解一级缓存和二级缓存之前请先开启Mybatis的log4j日记。或者熟悉断点调试的,也可以通过断点调试进行查看。
  • 对于查询缓存作用的认知:Mybatis提供查询的一级缓存和二级缓存,用于减轻数据库压力,减少因为多次执行相同sql语句时造成频繁的对数据库的操作,提高数据库的性能;而将查询信息存放在一定位置,当再次查询的时候可以直接拿取即可,这样也能提高查询的访问速度。

一级缓存、二级缓存有效范围在这里插入图片描述

一级缓存(同一个sqlsession):
  1. Mybatis默认是开启一级缓存的,不需要我们去手动开启,所以也就不需要去考虑配置问题了。而一级缓存是sqlsession级别的缓存,在操作数据库时首先需要利用SqlSessionFactory(会话工厂)建立SqlSession(会话)对象,在对象中有一个数据结构(HashMap本地缓存1)用来存储缓存数据,不同的SqlSession(会话)对象的存储数据的区域各不相同,因此彼此不会互相影响,所以由此可知:同一个SqlSession对象在向数据库查询到相应的数据之后,会将其缓存到自己的缓存内存当中,缓存未被清理的条件下,下次查询相同的数据的时候可以直接从自己的缓存当中直接拿出来即可,但不会去其它SqlSession对象中查找调用。

  2. 同个会话多次查询相同结果

		//查询
	public static void queryPersonById() throws IOException{
		//引入配置
		Reader reader = Resources.getResourceAsReader("org/entity/config/conf.xml");
		//建立会话工厂SqlSessionFactory
		SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
		//建立会话session
		SqlSession session = sessionFactory.openSession();
		PersonMapper queryPerson=session.getMapper(PersonMapper.class);
		//首次查询
		Person person =  queryPerson.queryPersonById(1);
		//同一个会话对象再次查询相同的信息
		Person person1 = queryPerson.queryPersonById(1);
		System.out.println(person);
		System.out.println(person1);
		session.close();
	}

一级缓存
根据运行可以发现,第一次查询访问了数据库,而第二次查询并没有,而是直接从缓存中拿取。

  1. 不同会话查询相同结果
		//建立第一个会话session
		SqlSession session = sessionFactory.openSession();
		PersonMapper queryPerson=session.getMapper(PersonMapper.class);
		Person person =  queryPerson.queryPersonById(1);
		System.out.println(person);
		Person person1 = queryPerson.queryPersonById(1);
		System.out.println(person1);
		//建立第二个会话session2
		SqlSession session2 = sessionFactory.openSession();
		PersonMapper queryPerson2=session2.getMapper(PersonMapper.class);
		Person person2 =  queryPerson2.queryPersonById(1);
		System.out.println(person2);
		session.close();

不同session查询相同结果
断点调试
断点调试2
通过以上可以证明不同的会话不会共用相同内存,而在断点调试中运行完第一次查询操作之后在executor > delegate > localCache中可以查看到查询后缓存的数据,而个人理解key中的包含的sql语句是判断下次查询的sql语句是否相同,相同则直接调用该缓存数据。如果有兴趣,可以在断点调试的时候选择进入方法体的调试方法,一步一步地进行,可以更加详细地查看到具体的语法的判断和执行。

而是否发现为什么要强调未清理的条件呢?
因为缓存被清理之后,内存中的数据缓存就不复存在,缓存被清空,而这时即使查询的数据在清理命令执行之前执行过,这时也只能重新向数据库中请求获取相应的数据,和第一次查询是一样的。

而清空一级缓存的方式有哪些?
1. 增删改命令所使用到的session.commit提交命令;这是为了防止脏数据造成不必要的麻烦;即增删改之后数据库中的数据就会发生相应的变化,而缓存中的数据并未发生变化,这时没有及时地修改就变成了脏数据,倘若没有提交命令执行清空缓存,很有可能再次查询做了修改后的数据却从缓存中得到未修改时的数据,这显然不是我们想要的结果。
2. 使用session.clearCache()命令进行主动清空缓存;

		//第一次向数据库请求
		Person person =  queryPerson.queryPersonById(1);
		System.out.println(person);
		//清理缓存
		session.clearCache();
		//再次查询时向数据库请求
		Person person1 = queryPerson.queryPersonById(1);
		System.out.println(person1);
		session.close();

可以发现在使用session.clearCache()之后,localCache中的缓存被清掉了。
主动清理
而总共提交了两次请求
在这里插入图片描述

  1. conf.xml全局设置标签< setting name=“localCacheScope” value=“STATEMENT”/>

MyBatis文档对于此标签的解释:
MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。

个人认为设置为value="STATEMENT"时,一级缓存的作用就失效了,查询不管多少次都要向数据库请求,即关闭了一级缓存,所以我对一级缓存无法关闭的说法表示怀疑。

  1. 在mapper.xml文件中的select语句中设置flushCache2为true,select默认是false而update、insert、delete中默认是ture。
//清除缓存
<select id="queryPersonById" resultType="Person" parameterType="int"  flushCache="true">

MyBatis文档对于此标签的解释:
select 元素允许你配置很多属性来配置每条语句的作用细节。
< select
id=“selectPerson”
parameterType=“int”
parameterMap=“deprecated”
resultType=“hashmap”
resultMap=“personResultMap”
flushCache=“false”
useCache=“true”
timeout=“10”
fetchSize=“256”
statementType=“PREPARED”
resultSetType=“FORWARD_ONLY”>
flushCache 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。

  1. 使用session.close()(关闭之后,之前运用session定义的调查查询就失效了,如果继续使用之前的查询调用就会出现报错的情况)
  2. 当session所在的方法体执行完毕时,即所在该方法的生存周期结束时就算没有调用到session.close(),一级缓存也会被清空。

二级缓存(同一个mapper、namespace)
  1. 二级缓存是mapper级别的缓存,多个sqlsession去操作同一个mapper(namespace)的sql语句时,则这些sqlsession会共用同一个二级缓存,即二级缓存的有效共用范围是在同一个mapper下。

  2. 之前学习Mybatis的时候学习到的二级缓存是需要设置全局(Setting)的,但我在偶然一次查了Mybatis的文档之后,发现二级缓存的设置只需要在mapper.xml中添加< cache/>即可:

Mybatis的文档中设置(settings)标签:
设置名:cacheEnabled
描述:全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存。
有效值:true | false
默认值:true

而且在执行的过程中,我尝试去掉全局设置中的标签:< setting name=“cacheEnabled” value=“true”/>似乎没有什么影响,但如果不放心的话,还是添加上去也是可以的。
开启步骤:
a. conf.xml中在< settings>添加< setting name=“cacheEnabled” value=“true”/>
b. 在具体的mapper.xml中 添加< cache/> 声明开启(PersonMapper.xml中)

   <mapper namespace="org.entity.mapper.PersonMapper">
   	<!--声明开启-->
   	<cache/>

c. 序列化相应的bean文件: 准备缓存的对象,必须实现了序列化接口 (如果开启的缓存Namespace=“org.entity.mapper.PersonMapper”),可知序列化对象为Person,因此需要将Person序列化 (序列化Person类,以及Person的级联属性、和父类),即在相应的bean中继承 Serializable,不序列化会使用二级缓存会报以下异常:
未序列化异常
而为什么要开启序列化,个人学习过程中听到的有两种说法:一种是二级缓存是直接存储到硬盘中去的,而将数据存储进硬盘要序列化,拿出来的时候回自动反序列化。而第二种说法是二级缓存储存的位置可以是在内存中,也可以是在硬盘上,但为了确保储存不出问题,需要序列化。

  1. 实例实现
//二级缓存查询
	public static void queryPersonById2() throws IOException{
		//引入配置
		Reader reader = Resources.getResourceAsReader("org/entity/config/conf.xml");
		//建立会话工厂SqlSessionFactory
		SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
		//建立三个会话session1、session2、session3
		SqlSession session1 = sessionFactory.openSession();
		SqlSession session2 = sessionFactory.openSession();
		SqlSession session3 = sessionFactory.openSession();

		//创建代理
		//第一次查询
		PersonMapper personMapper1 = session1.getMapper(PersonMapper.class);
		Person person1 = personMapper1.queryPersonById(2);
		System.out.println(person1);
		//session1.clearCache();
		Person person11 = personMapper1.queryPersonById(2);
		System.out.println(person11);
		//执行关闭操作,将sqlsession中的数据写到缓存区中去;
		session1.close();
		//证明不会session1.clearCache()不会清理掉二级缓存
		session1.clearCache();

		//第二次查询
		PersonMapper personMapper2 = session2.getMapper(PersonMapper.class);
		Person person2 = personMapper2.queryPersonById(2);
		System.out.println(person2);
		session2.close();

		//第三次查询
		PersonMapper personMapper3 = session3.getMapper(PersonMapper.class);
		Person person3 = personMapper3.queryPersonById(2);
		System.out.println(person3);
		session3.close();
	}

缓存命中率

  • 只有当执行了session.commit()或者session.close()命令之后,才会将数据提交到二级缓存中去,而首次查询之后的数据先是存放在一级缓存的的存储区域中,使用以上两个命令后清空一级缓存并将数据提交到二级缓存中去;之前两个语句都没有使用的时候,发现三个会话中的查询都是直接向数据库请求的,原因是数据没有提交到二级缓存中去。

  • 在以上的测试当中,开启二级缓存之后,DEBUG中会出现一个缓存命中率,计算是:从二级缓存中获得数据的次数/总共查询的次数(该);首次查询数据肯定不会命中的。而在多次更换session.clearCache()调用的位置测试之后可以发现 session.clearCache()只会清理一级缓存,并不会清理二级缓存

  • 在mapper.xml文件中的select语句中设置flushCache2为true时,每次查询都会向数据库请求;而在select语句中设置useCache="false"后,会禁用二级缓存,但不会关闭一级缓存。

  • 注意:二级缓存 的范围是同一个namespace, 如果有多个xxMapper.xml的namespace值相同,则通过这些xxxMapper.xml产生的xxMapper对象 仍然共享二级缓存。

  • 最后想说的是二级缓存还有整合ehcache等等内容,但不是很容易了解,有空再补充吧。

注:本篇文章是在Mybatis学习过程中总结整理出来的,用于以后对于Mybatis知识点的复习回顾,如有错漏之处敬请指出,不胜感激!


  1. 基于org.apache.ibatis.cache.impl.PerpetualCache 类的 HashMap本地缓存,其作用域是SqlSession ↩︎

  2. Mybatis对 flushCache文档注释:
    flushCache 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。

    ↩︎ ↩︎
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值