目录
一 简介
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 (收回策略)
LRU (最近最少使用的
):移除最长时间不被使用的对象,这是默认值。FIFO (先进先出)
:按对象进入缓存的顺序来移除它们。SOFT (软引用)
:移除基于垃圾回收器状态和软引用规则的对象。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"/>