查询缓存
一、mybatis的缓存理解
Mybatis的缓存,包括一级缓存和二级缓存:
一级缓存指的就是sqlsession,在sqlsession中有一个数据区域,是map结构,这个区域就是一级缓存区域。
一级缓存中的key是由sql语句、条件、statement等信息组成一个唯一值。一级缓存中的value,就是查询出的结果对象。
二级缓存指的就是同一个namespace下的mapper,二级缓存中,也有一个map结构,这个区域就是一级缓存区域。
一级缓存中的key是由sql语句、条件、statement等信息组成一个唯一值。一级缓存中的value,就是查询出的结果对象。
一级缓存是默认使用的。
二级缓存需要手动开启。
二、一级缓存
1、原理:
2、测试
@Test
public void testOneLevelCache() throws Exception {
// 创建UserMapper对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 由mybatis通过sqlsession来创建代理对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 第一次查询
User user1 = mapper.findUserById(1);
System.out.println(user1);
// 第二次查询
User user2 = mapper.findUserById(1);
System.out.println(user2);
sqlSession.close();
}
@Test
public void testOneLevelCache() throws Exception {
// 创建UserMapper对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 由mybatis通过sqlsession来创建代理对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 第一次查询
User user1 = mapper.findUserById(1);
System.out.println(user1);
// 执行添加用户操作
mapper.insertUser(user1);
// 执行commit时,将一级缓存清空
sqlSession.commit();
// 第二次查询
User user2 = mapper.findUserById(1);
System.out.println(user2);
sqlSession.close();
}
结果:
三、二级缓存
1、原理
2、开启二级缓存
<settings>
<!-- 二级缓存的总开关 -->
<setting name="cacheEnabled" value="true"/>
</settings>
<!-- 开启二级缓存,默认使用了PerpetualCache -->
<cache/>
3、序列化
public class User implements Serializable{
private int id;
private String username;// 用户姓名
private String sex;// 性别
private Date birthday;// 生日
private String address;// 地址
4、测试
@Test
public void testTwoLevelCache() throws Exception {
// 创建UserMapper对象
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
// 由mybatis通过sqlsession来创建代理对象
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);
// 第一次查询
User user1 = mapper1.findUserById(1);
System.out.println(user1);
// 在close的时候,才会将数据写入到二级缓存中
sqlSession1.close();
// 第二次查询
User user2 = mapper2.findUserById(1);
System.out.println(user2);
sqlSession2.close();
// 关闭资源
sqlSession3.close();
}
结果:
Cache Hit Ratio[com.hcx.mybatis.mapper.UserMapper]:0.0
只要出现该信息,说明二级缓存生效
@Test
public void testTwoLevelCache() throws Exception {
// 创建UserMapper对象
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
// 由mybatis通过sqlsession来创建代理对象
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);
// 第一次查询
User user1 = mapper1.findUserById(1);
System.out.println(user1);
// 在close的时候,才会将数据写入到二级缓存中
sqlSession1.close();
// 执行添加用户操作
mapper3.insertUser(user1);
// 执行commit时,将一级缓存清空
sqlSession3.commit();
// 第二次查询
User user2 = mapper2.findUserById(1);
System.out.println(user2);
sqlSession2.close();
// 关闭资源
sqlSession3.close();
}
结果:
Cache Hit Ratio[com.hcx.mybatis.mapper.UserMapper]:0.0
Cache Hit Ratio[com.hcx.mybatis.mapper.UserMapper]:0.0
两次缓存命中率都为0,说明二级缓存没有缓存该信息
5、禁用缓存
默认值是true
在映射文件UserMapper.xml中添加useCache<!-- 根据用户ID查询用户信息 -->
<select id="findUserById" parameterType="int" resultType="User" useCache="false">
SELECT * FROM USER WHERE id =#{id}
</select>
6、刷新缓存
<!-- 根据用户ID查询用户信息 -->
<!-- flushCache:刷新缓存,在select语句中,默认值是false,在增删改语句中,默认值是true -->
<select id="findUserById" parameterType="int" resultType="User" flushCache="true">
SELECT * FROM USER WHERE id =#{id}
</select>
7、整合ehcache
Mybatis本身是一个持久层框架,它不是专门的缓存框架,所以它对缓存的实现不够好,不能支持分布式。
Ehcache是一个分布式的缓存框架。
7.1分布式:系统为了提高性能,通常会对系统采用分布式部署(集群部署方式)
Cache是一个接口,它的默认实现是mybatis的PerpetualCache。如果想整合mybatis的二级缓存,那么实现Cache接口即可。
- ehcache-core-2.6.5.jar
- mybatis-ehcache-1.0.2.jar
<!-- 整合ehcache缓存框架-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
在config下,创建ehcache.xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!-- 缓存数据要存放的磁盘地址 -->
<diskStore path="F:\develop\ehcache" />
<!-- diskStore:指定数据在磁盘中的存储位置。
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>
@Test
public void testTwoLevelCache() throws Exception {
// 创建UserMapper对象
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
// 由mybatis通过sqlsession来创建代理对象
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);
// 第一次查询
User user1 = mapper1.findUserById(1);
System.out.println(user1);
// 在close的时候,才会将数据写入到二级缓存中
sqlSession1.close();
// 执行添加用户操作
// mapper3.insertUser(user1);
// // 执行commit时,将一级缓存清空
// sqlSession3.commit();
// 第二次查询
User user2 = mapper2.findUserById(1);
System.out.println(user2);
sqlSession2.close();
// 关闭资源
sqlSession3.close();
}
结果:
8、应用场景
使用场景:对于访问响应速度要求高,但是实时性不高的查询,可以采用二级缓存技术。
注意:在使用二级缓存的时候,要设置一下刷新间隔(cache标签中有一个flashInterval属性)来定时刷新二级缓存,
这个刷新间隔根据具体需求来设置,比如设置30分钟、60分钟等,单位为毫秒。
9、局限性
Mybatis二级缓存对细粒度的数据,缓存实现不好。
场景:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次查询都是最新的商品信息,此时如果使用二级缓存,
就无法实现当一个商品发生变化只刷新该商品的缓存信息而不刷新其他商品缓存信息,因为二级缓存是mapper级别的,
当一个商品的信息发送更新,所有的商品信息缓存数据都会清空。
解决此类问题,需要在业务层根据需要对数据有针对性的缓存。
比如可以对经常变化的数据操作单独放到另一个namespace的mapper中。