8、查询缓存(二级缓存)(mybatis笔记)
一、原理
1-
第一次
SqlSession1
去查询用户id
为1的用户信息,查询到用户信息将会将查询到的数据存储到二级缓存中。 -
第二次
SqlSession2
去查询用户id
为1的用户信息,先去缓存中找,如果缓存有则直接从缓存中取数据,如果没有则去数据库中查询。 -
二级缓存与一级缓存的区别,二级缓存的范围更大,多个
SqlSession
可以共享一个UserMapper
的二级缓存区域。UserMapper
有一个二级缓存区域(这个是按照namespace
划分),其他mapper
也有自己的二级缓存区域(按照namespace
划分)。每一个namespace
的mapper
有一个二级缓存区域。也就是说如果两个mapper
的namespace
相同,那么这两个mapper
执行sql
查询到的数据将存储在一个二级缓存区域中。 -
当然我们如果在两次查询之间加入提交操作,则同样会清空二级缓存的。这样第二次查询的时候也要发出
sql
去数据库中查询。
二、测试
- 开启二级缓存(工程
mybatis13
)
除了在SqlMapConfig.xml
中开启二级缓存的总开关,还要在具体的mapper.xml
中开启二级缓存。
SqlMapConfig.xml
<settings>
<!-- 打开延迟加载的开关,再将积极加载改为消极加载(即按需加载) -->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
<!-- 开启二级缓存,这里设置是为了方便管理,本身默认是开启的 -->
<setting name="cacheEnabled" value="true"/>
</settings>
UserMapper.xml
<!-- 开启本mapper的namespace下的二级缓存 -->
<cache />
说明:其实在全局配置文件中配置主要是为了便于管理。在UserMapper.xml
中开启二级缓存,UserMapper.xml
下的sql
执行完成会存储到它的缓存区域(HashMap
)。
索引 | 描述 | 允许值 | 默认值 |
---|---|---|---|
cacheEnabled |
对在此配置文件下的所有cache 进行全局性开/关设置 |
true/false |
true |
- 设置
pojo
类实现序列换接口,为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样,不一定在内存中。
User.java
public class User implements Serializable{
private Integer id ;
private String username ;
private String sex ;
private Date birthday ;
private String address ;
//用户创建的订单列表
private List<Orders> ordersList ;
....
}
- 测试
UserMapperTest.java
//二级缓存测试
@Test
public void testCache2() throws Exception{
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
//第一次发起请求,查询id为1的用户
User user1 = userMapper1.findUserById(1);
System.out.println(user1);
sqlSession1.close();//如果不关闭,SqlSession数据是不能写到二级缓存区域的
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
//第二次发起请求,查询id为1的用户
User user2 = userMapper2.findUserById(1);
System.out.println(user1);
sqlSession2.close();
}
此时的测试结果为:
可以看到总共只是发出了一条
sql
语句,同时注意到有一项信息Cache Hit Retio
,这表示缓存命中率,第一次的时候缓存中没有数据则命中率是0.0,第二次先从一级缓存中找,没有;再从二级缓存中找,找到了,此时缓存命中率是0.5。
- 加入提交
//二级缓存测试
@Test
public void testCache2() throws Exception{
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
//第一次发起请求,查询id为1的用户
User user1 = userMapper1.findUserById(1);
System.out.println(user1);
sqlSession1.close();//如果不关闭,SqlSession数据是不能写到二级缓存区域的
//执行提交操作
UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class);
User user = userMapper3.findUserById(1);
user.setUsername("狗蛋");
userMapper3.updateUser(user);
sqlSession3.commit();//执行提交清空UserMapper下的二级缓存
sqlSession3.close();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
//第二次发起请求,查询id为1的用户
User user2 = userMapper2.findUserById(1);
System.out.println(user1);
sqlSession2.close();
}
此时我们在测试的时候就会发现发出了两条查询语句。
三、二级缓存其他一些参数
3.1 禁用二级缓存
- 在
statement
中设置userCache="false"
可以禁用当前select
语句的二级缓存,即每次查询都会发出sql
去查询,默认情况是true
,即该sql
使用二级缓存。
<select id="findOrderListResultMap" resultMap="ordersUserMap" userCache = "false">
总结:针对每次查询都需要最新的数据,要设置成禁用二级缓存。
3.2 刷新缓存
-
在
mapper
的同一个namespace
中,如果有其它的insert、update、delete
操作数据后需要刷新缓存,如果不执行,则缓存中可能出现脏数据。 -
设置
statement
中的flushCache="true"
属性,默认情况下为true
,即刷新缓存,如果改成false
则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。如下:
<insert id="insertUser" parameterType="User" flushCache="true">
注意:这里刷新缓存就是清空缓存。
总结:一般情况下,执行完commit
操作都需要刷新缓存,flushCache="true"
表示刷新缓存,这样可以避免数据库脏读。
3.3 Mybatis Cache参数
-
flushInterval
(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒形成的时间段。默认情况下,也就是没有刷新问题,缓存仅仅调用语句时刷新。 -
size
(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是1024 -
readOnly
(只读)属性可以被设置为true
或false
。只读的缓存会给所有的调用者返回缓存对象的相同示例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false
。
如下例子:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
这个更高级的配置创建了一个FIFO
缓存,并每隔60秒刷新,存储结果对象或列表的512个引用,而且返回的对象被认为是只读,因此在不同线程中的调用者之间修改它们会导致冲突。可用的解决策略有,默认的是LRU
:
- 1.
LRU
:最近最少使用的:移除最长时间不被使用的对象 - 2.
FIFO
:先进先出:按对象进入缓存的顺序来移除它们 - 3.
SOFT
: 软引用:移除基于垃圾回收器状态和软引用规则的对象。 - 4.
WEAK
: 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
3.4 整合EhCache
EhCache是一个分布式的缓存框架。即使我们使用mybatis
的二级缓存,查询出来的相关数据也只是保存在单个服务器上,所以我们需要使用分布式的缓存。
- 导入相关的
jar
包:(工程mybatis14
)
ehcache-core-2.6.8.jar
mybatis-ehcache-1.0.3.jar
- 加入EhCache的配置文件
config/ehcache.xml
:
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<diskStore path="E:\ehcache" />
<defaultCache
maxElementsInMemory="10000"
eternal="false"
overflowToDisk="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxElementsOnDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
- 在相关的
UserMapper.xml
文件中进行配置:
<!-- 开启本mapper的namespace下的二级缓存,
type指定cache接口的实现类的接口,mybatis默认使用PerpetualCache类,我们要和
EhCache整合,需要配置type为EhCache的实现cache接口的类 -->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
之前我们只是开启了二级缓存,默认是使用mybatis
的二级缓存,这里我们配置使用EhCache
缓存。
- 测试
测试文件不需要改动,直接测试即可,我们发现还是可以实现二级缓存的效果。
四、二级缓存的应用场景
-
对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用
mybatis
的二级缓存技术降低数据库的访问量,提高访问速度,业务场景比如:耗时较高的统计分析sql
、电话账单查询sql
等。 -
实现方法如下:通过设置刷新间隔时间,由
mybatis
每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新间隔flushInterval
,比如设置为30分钟、60分钟等,根据需求而定。
五、局限性
Mybatis
二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis
的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其他商品的信息,因为mybatis
的二级缓存区域以mapper
为单位划分,当一个商品信息变化会将所有的商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存。