mybatis缓存机制
Mybatis提供一级缓存,二级缓存和自定义缓存:
一级缓存是SqlSession级别,其最大的共享范围就是一个SqlSession内部,如果多个SqlSession之间需要共享缓存,则需要使用到二级缓存。默认情况下,mybatis 的一级缓存是开启的。 二级缓存是mapper级别的缓存,对于mapper级别的缓存不同的sqlsession是可以共享的。 |
一.一级缓存
如下图所示,MyBatis会在一个SqlSession对象中创建一个本地缓存(local cache),对于每一次查询,都会尝试根据查询的条件去本地缓存中查找是否在缓存中,如果在缓存中,就直接从缓存中取出,然后返回给用户,从而减少资源浪费;否则,从数据库读取数据,将查询结果存入缓存并返回给用户,session关闭,缓存将被清空。
Mybatis一级缓存默认开启,无需配置。
测试案例1:
运行结果:
可以看到同一个sqlSession两次查询只是发出了一条sql
语句,第二次查询是从缓存中读取。
第二个newSqlSession查询的时候也是发出了sql
语句,因为缓存已经被清空了,由此可见mybatis一级缓存的sqlsession是不共享的。
测试案例2:
测试结果:
第一步 查询发起sql语句,并将查询结果放入缓存,
第二步对该语句进行更新操作,缓存将被清空
第三步再次查询信息,该语句又发起一次sql语句。
二.二级缓存
二级缓存可以使用MyBatis自己定义的二级缓存实现;也可以通过实现Cache接口自定义缓存(使用第三方内存缓存库,如redis)。
1. MyBatis自定义的二级缓存工作机制:
a).特点:即松散的Cache缓存管理和维护
一个Mapper中定义的增删改查操作只能影响到自己关联的Cache对象,如图所示namespace1产生的缓存只会被放置到相应关联的Cache1中,即Mappernamespace2,namespace3,namespace4 中的CRUD的语句不会影响到Cache1。
b).Mapper之间的缓存关系比较松散,相互关联的程度比较弱
如果将AMapper和BMapper共用一个Cache对象,当BMapper执行更新操作时,可以清空对应Cache中的所有的缓存数据,使缓存使用效率变的很低!AMapper和BMapper的任意的更新操作都会将共用的Cache清空,会频繁地清空Cache,导致Cache实际的命中率和使用率就会变得很低,这种策略实际情况下是不可取的。
相关配置:
SqlSessionFactory层面上的二级缓存是不开启的,二级缓存开启需要配置。
1.使用注解@CacheNamespace方式配置,不写type就使用mybatis默认的缓存,也可以去实现 Cache 接口来自定义缓存。:
@CacheNamespace注解详情:
Implementation: 实现缓存功能的类
PerpetualCache这个类是mybatis默认实现二级缓存功能的类
Eviction:缓存回收策略(可选值有"LRU"、"FIFO"、"SOFT"、"WEAK",默认值是LRU
LruCache:最近最少使用,移除最长时间不被使用的对象.
FIFO:先进先出,按对象进入缓存的顺序来移除
SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象
WEAK:弱引用,更积极地移除基于垃圾收集器状态和弱引用规则的对象
flushInterval: 指缓存过期时间,单位为毫秒.
默认为0,即只要容量足够,永不过期
Size:指缓存多少个对象
默认值为1024
readWrite:可读/可写
默认为true
Bloking:锁
默认为false
2.对于MyBatis自带的二级缓存,实体类可以不用实现可序列化接口。
测试案例1:
测试结果:
创建两个sqlSession对象,
第一次查询发出sql语句,将查询结果翻入缓存中,关闭第一个sqlsession,用第二个sqlsession发起查询,数据是从缓存中读取的,由此可见mybatis二级缓存sqlSession之间是共享的.
测试案例2:
测试结果:
第一步sqlSessionOne进行sql查询操作,将查询结果放入缓存中
第二步sqlSessionTwo执行更新操作,缓存将被清空
第三步sqlSessionThree进行查询操作,重新发起sql查询。
执行更新操作,二级缓存数据将被清空
三.自定义缓存Redis
为了提高扩展性。MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存,通过重写Cache接口中的方法,将mybatis中默认的缓存空间映射到redis空间中。
相关配置:
自定义类MybatisCache通过重写Cache接口中的方法,自定义缓存.
flashInterval:指缓存过期时间,单位为毫秒,60000即为60秒
2.自定义缓存redis有时会将数据缓存到硬盘上,需要实现 Serializable 序列化接口
开启了二级缓存后,还需要将要缓存的pojo实现Serializable接口,为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样,不一定只存在内存中,有可能存在硬盘中,如果我们要再取这个缓存的话,就需要反序列化了。所以mybatis中的pojo需要实现Serializable接口。
测试案例1:
测试结果:
第一步sqlSessionOne进行sql查询操作,将查询结果放入redis缓存中
Redis查看结果,数据已被序列化:
第二步sqlSessionTwo执行更新操作,缓存将被清空
Redis查看结果:
第三步sqlSessionThree进行查询操作,重新发起sql查询。
执行更新操作,二级缓存redis数据将被清空
打开Redis查看结果:
禁用二级缓存使用@Options注解,将useCache属性设置为false
options有个timeout
mybatis如果不指定,默认超时时间是不做限制的,默认值为-1.
以秒为单位,当超出了设置的超时时间时,会抛出SQLTimeoutException
#为什么不用mybatis原生态缓存?
因为mybatis原生态缓存有局限性,mybaits的二级缓存区域是以mapper为单位划分,比如某列表使用了join链接查询,当对其中一个表(做了增删改操作)数据发生变动时,会将cache全部清空,从而倒导致cache实际命中率和使用率变低,这种策略不符合我们的业务场景。
测试案例1,使用mybatis原生态缓存:
测试结果,多次修改表1,表2数据始终使用sql查询:
测试案例2,使用redis缓存:
需注释掉cache接口的清空缓存clear()方法,因为每次增删改都清空缓存会消耗缓存的内存资源
测试结果:
1. 第一次执行联表查询将数据放入到缓存中,后对表1进行更新操作,对表2执行sql查询,发现数据是从缓存中读取。