Mybatis缓存(二)
Mybatis的二级缓存
1、二级缓存的范围
1. 二级缓存是Mapper级别的缓存。
2. 二级缓存的结构也是一个HashMap。
3. 不同的SqlSession对象去操作同一个mapper中的SQL语句,多个SqlSession共用二级缓存。
4. 二级缓存的作用域是mapper的同一个namespace,如果两个mapper文件的namespace相同,则这两个共用二级缓存。
5. Mybatis的二级缓存需要配置开启。
2、工作原理
说明:
1. 当SqlSession1一次执行查询的时候,首先会去查询二级缓存,如果二级缓存中没有数据,然后才回去查询数据库,数据库返回后会将返回的数据保存到二级缓存之中。
2. 第二次SqlSession2再去查询相同的数据时,就会直接从二级缓存中取数据,不再需要去查询数据库。
3. 如果在两次查询之间执行了SqlSession3的commit操作(执行插入、更新、删除),则会清空二级缓存,这样是为了防止读到脏数据。
3、一级缓存的测试
1. 开启二级缓存
mybaits的二级缓存是mapper范围级别,除了在SqlMapConfig.xml设置二级缓存的总开关,还要在具体的mapper.xml中开启二级缓存。
2. SqlMapConfig.xml中开启二级缓存
<settings>
<!--开启二级缓存-->
<setting name="cacheEnabled" value="true" />
</settings>
3. po实现序列化
二级缓存需要查询结果映射的pojo对象实现java.io.Serializable接口实现序列化和反序列化操作,注意如果存在父类、成员pojo都需要实现序列化接口。
4. mapper.xml文件中配置缓存
<mapper namespace="com.jack.dao.Mapper">
<!--开启本mapper的二级缓存-->
<cache />
<select id="findUser" parameterType="int" resultType="User">
SELECT * FROM User WHERE id = #{value}
</select>
<select id="updateUser" parameterType="User" >
UPDATE User SET username=#{username} WHERE id = #{id}
</select>
</mapper>
5. mapper接口文件
public User findUser(int id) throws Exception;
public User updateUser(User user) throws Exception;
6. 测试代码
public void testErCache() throws Exception{
SqlSession session1 = sessionFactory.openSession();
Mapper mapper1 = session1.getMapper(Mapper.class);
//第一次查询,会发送SQL语句,去数据库查询
User user1 = mapper1.findUser(1);
System.out.println(user1);
//必须执行SqlSession的close操作,才会将数据写入二级缓存
session1.close();
//执行修改的commit操作
SqlSession session2 = sessionFactory.openSession();
Mapper mapper2 = session2.getMapper(Mapper.class);
User user2 = mapper2.findUser(1);
user2.setUsername("小明");
mapper2.updateUser(user2);
session2.commit();
session2.close();
//第二次查询,如果没有执行commit操作,直接在缓存中取数据,不会发生sql语句查询数据库
//如果执行了commit操作,会发生sql语句查询数据库
SqlSession session3 = sessionFactory.openSession();
Mapper mapper3 = session3.getMapper(Mapper.class);
User user3 = mapper3.findUser(1);
System.out.println(user3);
session3.close();
}
4、禁用二级缓存
在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。
<select id="findUser" parameterType="int" resultType="User" useCache="false">
SELECT * FROM User WHERE id = #{value}
</select>
5、刷新缓存
在mapper的同一个namespace中,如果有其它insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。
设置statement配置中的flushCache="true" 属性,默认情况下为true即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。
<select id="findUser" parameterType="int" resultType="User" useCache="false" flushCache="true">
SELECT * FROM User WHERE id = #{value}
</select>
6、Mybatis Cache参数详解
(1)flushInterval(刷新间隔):可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
(2)size(引用数目):可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是1024。
(3)readOnly(只读)属性:可以被设置为true或false。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。
如下例子:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会导致冲突。可用的收回策略有, 默认的是 LRU:
- LRU – 最近最少使用的:移除最长时间不被使用的对象。
- FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
- SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
- WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
7、二级缓存的应用场景
对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用mybatis二级缓存技术降低数据库访问量,提高访问速度,业务场景比如:耗时较高的统计分析sql、电话账单查询sql等。
实现方法如下:通过设置刷新间隔时间,由mybatis每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新间隔flushInterval,比如设置为30分钟、60分钟、24小时等,根据需求而定。
8、二级缓存的局限性
mybatis二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存。
9、使用ehcache来管理Mybatis的缓存
1. ehcache简介
Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP api等特点。
2. 与Mybatis联合的原理
Mybatis提供了一个拥有管理缓存的接口Cache,只要实现Cache接口,就可以自己来处理Mybatis的缓存逻辑,ehcache实现了该接口。
3. 实现步骤:
(1). 加入ehcache的jar包。
(2). 创建ehcache的配置文件
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!--
指定数据在磁盘中的存储位置
-->
<diskStore path="F:\develop\ehcache" />
<!--
defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用<defalutCache/>指定的的管理策略
以下属性是必须的:
maxElementsInMemory - 在内存中缓存的element的最大数目
maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大
eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断
overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上
以下属性是可选的:
timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大
timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.
diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作
memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)
-->
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
(3). 在mapper.xml指定ehcache为缓存处理器
<!--
开启本mapper的二级缓存
指定缓存管理为ehcache
property可以不用进行配置
-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache">
<property name="timeToIdleSeconds" value="3600"/>
<property name="timeToLiveSeconds" value="3600"/>
<!-- 同ehcache参数maxElementsInMemory -->
<property name="maxEntriesLocalHeap" value="1000"/>
<!-- 同ehcache参数maxElementsOnDisk -->
<property name="maxEntriesLocalDisk" value="10000000"/>
<property name="memoryStoreEvictionPolicy" value="LRU"/>
</cache>