Mybatis的一级缓存和二级缓存详解

Mybatis 同时被 2 个专栏收录
18 篇文章 4 订阅
3 篇文章 0 订阅

注:本笔记是根据尚硅谷的MyBatis视频记录的

对于任何一个持久层框架,都有缓存机制;缓存在电脑中有一块真实的存储空间(https://baike.baidu.com/item/%E7%BC%93%E5%AD%98/100710?fr=aladdin);

两个关于mybatis缓存额外的链接:

关于Mybatis的一级缓存和二级缓存执行顺序具体可参考:Mybatis的一级缓存和二级缓存执行顺序

 Mybatis整合第三方缓存步骤具体可参考:Mybatis整合第三方缓存ehcache

缓存原理图:

一、一级缓存(本地缓存)

sqlSession级别的缓存。(相当于一个方法内的缓存)

每一次会话都对应自己的一级缓存,作用范围比较小,一旦会话关闭就查询不到了;

一级缓存默认是一直开启的,是SqlSession级别的一个Map;
与数据库同一次会话期间查询到的数据会放在本地缓存中。
以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库;

测试:

取缓存中的数据:

说明:当再次查询发现已经有数据了,就直接在缓存中返回之前查的数据,而不再访问数据库;

二、一级缓存失效的四种情况:

没有使用到当前一级缓存的情况,效果就是:还需要再向数据库发出查询

1、sqlsession不同(会话不同)

结果:

说明:不同的会话的缓存不共享数据

2、sqlsession相同,查询缓存中没有的数据

@Test
public void testFirstLevelCache() throws IOException{
	SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
	SqlSession openSession = sqlSessionFactory.openSession();
	try{
		EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
		Employee emp01 = mapper.getEmpById(1);
		System.out.println(emp01);
		
		//sqlSession相同,查询条件不同
		Employee emp03 = mapper.getEmpById(3);
		System.out.println(emp03);
		System.out.println(emp01==emp03);

	}finally{
		openSession.close();
	}
}

结果:

说明:当缓存中没有数据时,会重新查数据库

3、sqlsession相同,但两次查询之间执行了增删改操作

@Test
public void testFirstLevelCache() throws IOException{
	SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
	SqlSession openSession = sqlSessionFactory.openSession();
	try{
		EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
		Employee emp01 = mapper.getEmpById(1);
		System.out.println(emp01);
		
		//sqlSession相同,两次查询之间执行了增删改操作(这次增删改可能对当前数据有影响)
		mapper.addEmp(new Employee(null, "testCache", "cache", "1"));
		System.out.println("数据添加成功");
		
		Employee emp02 = mapper.getEmpById(1);
		System.out.println(emp02);
		System.out.println(emp01==emp02);
		
	}finally{
		openSession.close();
	}
}

结果:

说明:为了防止增删改对当前数据的影响,即使查的同一个对象,也会重新查数据库

原因:每个增删改标签都有默认清空缓存配置:flushCache="true",不过这是默认的是一级和二级缓存都清空

4、sqlsession相同,但手动清楚了一级缓存(缓存清空)

清空缓存:openSession.clearCache();

@Test
public void testFirstLevelCache() throws IOException{
	SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
	SqlSession openSession = sqlSessionFactory.openSession();
	try{
		EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
		Employee emp01 = mapper.getEmpById(1);
		System.out.println(emp01);
		
		//sqlSession相同,手动清除了一级缓存(缓存清空)
		openSession.clearCache();
		
		Employee emp02 = mapper.getEmpById(1);
		System.out.println(emp02);
		System.out.println(emp01==emp02);
		
	}finally{
		openSession.close();
	}
}

结果:

说明:手动清空缓存后,需要重新查数据库

三、二级缓存(全局缓存)

基于namespace名称空间级别的缓存:一个namespace对应一个二级缓存

即一个mapper.xml对应一个缓存:

1、工作机制:

*         1、一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中;
*         2、如果会话关闭;一级缓存中的数据会被保存到二级缓存中;新的会话查询信息,就可以参照二级缓存中的内容;
*         3、sqlSession===EmployeeMapper==>Employee
*                         DepartmentMapper===>Department
*             不同namespace查出的数据会放在自己对应的缓存中(map)
*             效果:数据会从二级缓存中获取
*                 查出的数据都会被默认先放在一级缓存中。
*                 只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中

2、 使用:
*             1)、开启全局二级缓存配置:<setting name="cacheEnabled" value="true"/>
*             2)、去mapper.xml中配置使用二级缓存:
*                 <cache></cache>
*             3)、我们的POJO需要实现序列化接口

1)在mybatis全局配置文件中开启全局二级缓存配置:<setting name="cacheEnabled" value="true"/>

2)在mapper.xml中配置使用二级缓存

直接加上: <cache><cache/>

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.mybatis.dao.EmployeeMapper">
	
	 <cache><cache/>

 	<!--public Map<Integer, Employee> getEmpByLastNameLikeReturnMap(String lastName);  -->
 	<select id="getEmpByLastNameLikeReturnMap" resultType="com.atguigu.mybatis.bean.Employee">
 		select * from tbl_employee where last_name like #{lastName}
 	</select>
</mapper>

或者 <cache>中配置一些参数:

eviction:缓存的回收策略:
    • LRU – 最近最少使用的:移除最长时间不被使用的对象。
    • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
    • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
    • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
    • 默认的是 LRU。
flushInterval:缓存刷新间隔
    缓存多长时间清空一次,默认不清空,设置一个毫秒值
readOnly:是否只读:
    true:只读;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。
             mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,速度快
    false:非只读:mybatis觉得获取的数据可能会被修改。
            mybatis会利用序列化&反序列的技术克隆一份新的数据给你。安全,速度慢
size:缓存存放多少元素;
type="":指定自定义缓存的全类名;
        实现Cache接口即可;

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.mybatis.dao.EmployeeMapper">

	 <cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"></cache> 
 
 	<!--public Map<Integer, Employee> getEmpByLastNameLikeReturnMap(String lastName);  -->
 	<select id="getEmpByLastNameLikeReturnMap" resultType="com.atguigu.mybatis.bean.Employee">
 		select * from tbl_employee where last_name like #{lastName}
 	</select>
</mapper>

3)POJO需要实现序列化接口

测试:

注意:需要openSession.close();后,才能从二级缓存中查数据;

@Test
public void testSecondLevelCache() throws IOException{
	SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
	SqlSession openSession = sqlSessionFactory.openSession();
	SqlSession openSession2 = sqlSessionFactory.openSession();
	try{

		EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
		EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);
		
		Employee emp01 = mapper.getEmpById(1);
		System.out.println(emp01);
		openSession.close();
		
		//第二次查询是从二级缓存中拿到的数据,并没有发送新的sql
		Employee emp02 = mapper2.getEmpById(1);
		System.out.println(emp02);
		openSession2.close();
		
	}finally{
		
	}
}

结果:

说明:第二次查询是从二级缓存中拿到的数据,并没有发送新的sql;

注意:

如果openSession.close();在第二次查询之后才关闭,则第二次查询会从一级缓存中查,如果不是一个session,则查询不到数据:

@Test
public void testSecondLevelCache() throws IOException{
	SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
	SqlSession openSession = sqlSessionFactory.openSession();
	SqlSession openSession2 = sqlSessionFactory.openSession();
	try{

		EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
		EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);
		
		Employee emp01 = mapper.getEmpById(1);
		System.out.println(emp01);
		
		Employee emp02 = mapper2.getEmpById(1);
		System.out.println(emp02);
		openSession.close();
		openSession2.close();
		
	}finally{
		
	}
}

结果:

说明:第二次又重新发送了sql,因为从二级缓存中取数据时,会话没关闭所以二级缓存中没数据,所以又去一级缓存中查询,也没有数据则发送了sql查数据库;

所以,只有会话关闭或提交后,一级缓存中的数据才会转移到二级缓存中,然后因为是同一个namespace所以可以获取到数据;

关于Mybatis的一级缓存和二级缓存执行顺序具体可参考:Mybatis的一级缓存和二级缓存执行顺序

四、和缓存有关的设置/属性

1)、mybatis全局配置文件中配置全局缓存开启和清空

1.1)控制二级缓存的开启和关闭

         <setting name="cacheEnabled" value="true"/>

         cacheEnabled=true:开启缓存;false:关闭缓存(二级缓存关闭)(一级缓存一直可用的)

1.2)控制一级缓存的开启和关闭

         <setting name="localCacheScope" value="SESSION"/>

         localCacheScope:本地缓存作用域(一级缓存SESSION);

         当前会话的所有数据保存在会话缓存中;STATEMENT:可以禁用一级缓存;

注意:一级缓存关闭后,二级缓存自然也无法使用;  

2)、方法中sqlSession清除缓存测试

sqlSession.clearCache();只是清除当前session的一级缓存;

如果openSession清空了缓存,即执行了openSession.clearCache()方法:

结果:

 说明:openSession清空缓存不影响二级缓存;只清空了一级缓存;因为在openSession.close()时,就将一级缓存保存至了二级缓存; 

3)、mapper.xml中也可以配置一级和二级缓存开启和使用

3.1)每个select标签都默认配置了useCache="true":
         如果useCache= false:则表示不使用缓存(一级缓存依然使用,二级缓存不使用)
3.2)每个增删改标签默认配置了flushCache="true":(一级二级都会清除)


增删改执行完成后就会清除缓存;

测试:默认flushCache="true":一级缓存和二级缓存都会被清空;

执行增加操作:

结果:

注意:查询标签<select>默认flushCache="false":如果flushCache=true;每次查询之后都会清空缓存;一级和二级缓存都无法使用;

五、整合第三方缓存

mybatis通过map实现的缓存,很不专业;此时可以通过整合第三方缓存来达到缓存的目的;

做法:实现Cache.java接口中的方法即可:

如实现putObject()方法,往缓存中写数据;实现getObject()方法,从缓存中获取数据;至于什么时候调用,由mybatis决定;

 具体可参考:Mybatis整合第三方缓存ehcache

评论 5 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:创作都市 设计师:CSDN官方博客 返回首页

打赏作者

林海静

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值