MyBatis从入门到精通笔记(6)MyBatis缓存

一 简介

MyBatis的缓存有一级缓存和二级缓存。
一级缓存默认开启(sqlsession级别);二级缓存默认不开启,并且可以结合其他技术。

二 一级缓存

查看测试代码

public void testL1Cache(){
		//获取 sqlSession
		SqlSession sqlSession = getSqlSession();
		SysUser user1 = null;
		try {
			//获取 UserMapper 接口
			UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
			//调用 selectById 方法,查询 id = 1 的用户
			user1 = userMapper.selectById(1l);
			//对当前获取的对象重新赋值
			user1.setUserName("New Name");
			//再次查询获取 id 相同的用户
			SysUser user2 = userMapper.selectById(1l);
			//虽然我们没有更新数据库,但是这个用户名和我们 user1 重新赋值的名字相同了
			Assert.assertEquals("New Name", user2.getUserName());
			//不仅如何,user2 和 user1 完全就是同一个实例
//			Assert.assertEquals(user1, user2);
			System.out.println(user2 == user1);
		} finally {
			//关闭当前的 sqlSession
			sqlSession.close();
		}
		System.out.println("开启新的 sqlSession");
		//开始另一个新的 session
		sqlSession = getSqlSession();
		try {
			//获取 UserMapper 接口
			UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
			//调用 selectById 方法,查询 id = 1 的用户
			SysUser user2 = userMapper.selectById(1l);
			//第二个 session 获取的用户名仍然是 admin
			Assert.assertNotEquals("New Name", user2.getUserName());
			//这里的 user2 和 前一个 session 查询的结果是两个不同的实例
			Assert.assertNotEquals(user1, user2);
			//执行删除操作
			userMapper.deleteById(2L);
			//获取 user3
			SysUser user3 = userMapper.selectById(1l);
			//这里的 user2 和 user3 是两个不同的实例
//			Assert.assertNotEquals(user2, user3);
			System.out.println(user2 == user3);
		} finally {
			//关闭 sqlSession
			sqlSession.close();
		}

//关键输出
true
开启新的 sqlSession
false

原因
MyBatis 的一级缓存存在于SqlSession 的生命周期中,在同一个SqlSession 中查询时,MyBatis 会把执行的方法和参数通过算法生成缓存的键值,将键值和查询结果存入一个Map对象中。
如果同一个SqlSession 中执行的方法和参数完全一致,那么通过算法会生成相同的键值,当Map 缓存对象中己经存在该键值时,则会返回缓存中的对象

取消一级缓存

	<select id="selectById" resultMap="userMap" flushCache="true">
		select * from sys_user where id = #{id}
	</select>
  • 设置该属性后,会在查询数据前清空当前的一级缓存.会影响当前SqlSession 中所有缓存的查询,需要谨慎使用
  • 任何的INSERT 、UPDATE、DELETE 操作都会清空一级缓存

三 使用默认二级缓存

二级缓存存在于SqlSessionFactory 的生命周期中,MyBatis默认支持的二级缓存时放在Map中的。

MyBatis 的二级缓存是和命名空间绑定的,即二级缓存需要配置在Mapper.xml 映射文件中,或者配置在Mapper.java接口中。在映射文件中,命名空间就是XML根节点mapper 的namespace 属性。在Mapper 接口中,命名空间就是接口的全限定名称

3.1 全局配置

这个参数默认就是开启的,下面的配置不写也没事。当下面的value为false时,则关闭了二级缓存

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

3.2 配置效果/参数

默认效果

在这里插入图片描述

回收参数

eviction (收回策略)

  1. LRU (最近最少使用的):移除最长时间不被使用的对象,这是默认值。
  2. FIFO (先进先出):按对象进入缓存的顺序来移除它们。
  3. SOFT (软引用):移除基于垃圾回收器状态和软引用规则的对象。
  4. WEAK (弱引用):更积极地移除基于垃圾收集器状态和弱引用规则的对象

flushinterval (刷新间隔)
可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况不设置,即没有刷新间隔,缓存仅仅在调用语句时刷新。

size (引用数目)
可以被设置为任意正整数,要记住缓存的对象数目和运行环境的可用内存资源数目。默认值是1024

readOnly (只读)
属性可以被设置为true 或false 。只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,这提供了很重要的性能优势。可读写的缓存会通过序列化返回缓存对象的拷贝,这种方式会慢一些,但是安全,因此默认是false
因此此时,缓存的对象类需要实现序列化接口

缓存时机
当调用close 方法关闭SqlSession 时,SqlSession 才会保存查询数据到二级缓存中。在这之后二级缓存才有了缓存数据。

3.3 Mapper.xml中配置二级缓存

<mapper namespace="zyc.mybatis.simple.mapper.UserMapper">

    <cache/>
    ...

这样就开启了二级缓存。

注意脏数据

就是我们对查询后到的对象做了无意义的修改(不会保存),然后关闭sqlsession。这时,二级缓存中的缓存value就是我们刚刚修改后的对象。如果这些修改时没有意义的,那么其他查询就会得到一个脏数据(不是数据库中真正的值

注意和一级缓存共同生效

如果我们在先一个sqlsession中以相同的参数查询了多次,那么此时:返回的对象时同一个实例.这是因为一级缓存仍在生效

3.4 Mapper 接口中配置二级缓存

有时我们可能会使用注解方式的mybatis。

只使用了接口,没有映射文件

只需要像下面这样就可以了。

//其他参数也可以配置
@CacheNamespace(size = 512)
public interface RoleMapper {

同时使用了接口和映射文件

实现需求:既需要在接口上增加CacheNamespace注解,又要在mapper.xml中增加cache标签。

这时直接按上面的需求实现就会报错。
在这里插入图片描述

使用参照缓存

这是因为Mapper 接口和对应的XML 文件是相同的命名空间。这个时候应该使用参照缓存。在Mapper 接口中,参照缓存配置如下。

//@CacheNamespace(size = 512)
@CacheNamespaceRef(RoleMapper.class)
public interface RoleMapper {

这样,RoleMapper 接口中的注解方法和XML 中的方法就使用了相同的缓存。这时是接口引用了xml中配置的二级缓存。反之就是

<cache-ref namespace= "tk.rnybatis.simple.mapper.RoleMapper"/>

MyBatis 中很少会同时使用Mapper 接口注解方式和XML 映射文件,所以参照缓存并不是为了解决这个问题而设计的。参照缓存除了能够通过引用其他缓存减少配置外,主要的作用是解决脏读??

四 集成EhCache

4.1 EhCache介绍

Java进程内的缓存框架

  • 快速
  • 简单
  • 多种缓存策略
  • 支持磁盘缓存,容量大
  • 重启时会从磁盘读取历史缓存,缓存不丢失
  • 支持分布式缓存
  • 有侦听接口

4.2 整合步骤

添加依赖

        <!-- EhCache 缓存框架 -->
        <dependency>
            <groupId>org.mybatis.caches</groupId>
            <artifactId>mybatis-ehcache</artifactId>
            <version>1.0.3</version>
        </dependency>

在resources目录下添加ehcache.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="ehcache.xsd"
    updateCheck="false" monitoring="autodetect"
    dynamicConfig="true">
    
    <diskStore path="D:/cache" />
            
	<defaultCache      
		maxElementsInMemory="3000"      
		eternal="false"      
		copyOnRead="true"
		copyOnWrite="true"
		timeToIdleSeconds="3600"      
		timeToLiveSeconds="3600"      
		overflowToDisk="true"      
		diskPersistent="true"/> 

	<cache      
		name="zyc.mybatis.simple.mapper.RoleMapper"
		maxElementsInMemory="3000"      
		eternal="false"      
		copyOnRead="true"
		copyOnWrite="true"
		timeToIdleSeconds="3600"      
		timeToLiveSeconds="3600"      
		overflowToDisk="true"      
		diskPersistent="true"/> 
</ehcache>

在mappe.xml中使用

<mapper namespace="zyc.mybatis.simple.mapper.RoleMapper">
    <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
		...
</mapper>

参数说明

copyOnRead和copyOnWrite
表示读写时,是使用副本还是原始对象。如果在缓存后会对缓存对象进行修改,或者拿到缓存对象后会修改该对象,那么就会产生脏数据。这时最好开启这两个属性。(默认都是false)

4.3 说明

  • 在对cache标签设置type,使用ehcache后,则其他属性都不用配置(配置也会没有效果

ehcache可以配置多个cache。mybatis会优先将数据缓存到以当前映射文件的命名空间为name的cache中,如果找不到这个cache。则存储到defaultCache中
所以我们可以为不同的mapper配置独立的cache(二级缓存)。

五 脏数据的产生和避免

5.1 产生

MyBatis 的二级缓存是和命名空间绑定的,所以通常情况下每一个Mapper 映射文件都拥有自己的二级缓存,不同Mapper 的二级缓存互不影响。
在常见的数据库操作中,多表联合查询非常常见,由于关系型数据库的设计,使得很多时候需要关联多个表才能获得想要的数据。在关联多表查询时肯定会将该查询放到某个命名空间下的映射文件中,这样一个多表的查询就会缓存在该命名空间的二级缓存中。涉及这些表的增、删、改操作通常不在一个映射文件中,它们的命名空间不同,因此当有数据变化时,多表查询的缓存未必会被清空,这种情况下就会产生脏数据。

5.2 避免办法-参照缓存

当某几个表可以作为一个业务整体时,通常是让几个会关联的ER 表同时使用同一个二级缓存

类似下面这种,将多个映射配置到一个cache中

<cache-ref namespace= "tk.rnybatis.simple.mapper.RoleMapper"/>

参考

  1. 《MyBatis 从入门到精通》
  2. 源码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值