Mybatis缓存
Mybatis的缓存,包括一级缓存
和二级缓存
一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
.
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
一级缓存是默认使用的。
二级缓存需要手动开启。
一级缓存(SqlSession级别)
第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。
得到用户信息,将用户信息存储到一级缓存中。
如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。
测试代码1
@Test
public void testLevelOneCache() {
//......
User user1 = userMapper.findUserById(1);
User user2 = userMapper.findUserById(1);
//......
}
小结:由日志信息可知,进行两次相同的查询语句,只进行一次数据库查询,说明存在一级缓存
测试代码2
@Test
public void testLevelOneCache() {
//......
User user1 = userMapper.findUserById(1);
userMapper.addUser(user1);
User user2 = userMapper.findUserById(1);
//......
}
小结:在两次相同的查询之间进行插入操作,导致插入后清除一级缓存,第二次查询便得再次查询数据库
二级缓存(Mapper级别)
未开启二级缓存
@Test
public void testLevelTwoCache() {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
User user1 = mapper1.findUserById(1);
User user2 = mapper2.findUserById(1);
sqlSession1.close();
sqlSession2.close();
}
小结:一级缓存默认开启,二级缓存没有手动开启,而这是两个sqlsession的查询,所以一级缓存不起作用,查询了两次数据库
开启二级缓存
1.配置全局配置文件
<settings>
<!-- 开启二级缓存总开关,默认是false -->
<setting name="cacheEnabled" value="true"/>
</settings>
2.配置映射文件
<!-- 开启二级缓存,默认使用的是PerpetualCache -->
<cache/>
<!--
cache元素用来开启当前mapper的namespace下的二级缓存,该元素的属性设置如下:
flushInterval:刷新间隔,可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段,默认情况下是不设置的,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
size:缓存数目,可以被设置为任意正整数,要记住你的缓存对象数目和你运行环境可用内存资源数目,默认值是1024.
readOnly:只读,属性可以被设置为true或false,只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改。这提供了很重要的性能优势,可读写的缓存会返回缓存对象的拷贝(通过序列化),这会慢一些,但是安全,因此默认是false。
eviction:收回策略,默认为LRU,有如下几种:
LRU:最近最少使用的策略,移除最长时间不被使用的对象。
FIFO:先进先出策略,按对象进入缓存的顺序来移除它们。
SOFT:软引用策略,移除基于垃圾回收器状态和软引用规则的对象。
WEAK:弱引用策略,更积极地移除基于垃圾收集器状态和弱引用规则的对象。
-->
3.确保po类实现序列化
public class User implements Serializable {
...
}
注意:使用二级缓存时,与查询结果映射的java对象必须实现java.io.Serializable接口的序列化和反序列化操作,如果存在父类,其成员都需要实现序列化接口,实现序列化接口是为了对缓存数据进行序列化和反序列化操作,因为二级缓存数据存储介质多种多样,不一定在内存,有可能是硬盘或者远程服务器。
测试代码1
@Test
public void testLevelTwoCache() {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
User user1 = mapper1.findUserById(1);
sqlSession1.commit();
User user2 = mapper2.findUserById(1);
sqlSession2.close();
}
小结:两个的sqlsession查询相同内容,只查询了一次数据库,说明使用了二级缓存
测试代码2
@Test
public void testLevelTwoCache() {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);
User user1 = mapper1.findUserById(1);
sqlSession1.commit();
mapper3.addUser(user1);
sqlSession3.commit();
User user2 = mapper2.findUserById(1);
sqlSession1.close();
sqlSession2.close();
sqlSession3.close();
}
小结:说明二级缓存会在增加、删除、修改后清除缓存
禁用二级缓存
在statement中设置userCache=false
,可以禁用当前select语句的二级缓存,即每次查询都是去数据库中查询,默认情况下是true,即该statement使用二级缓存。
刷新二级缓存
在statement中设置flushCache=true
可以刷新当前的二级缓存,默认情况下如果是select语句,那么flushCache是false。如果是insert、update、delete语句,那么flushCache是true。这也就是为什么默认情况下进行增删改会使二级缓存失效。
1.如果查询语句设置成true,那么每次查询都是去数据库查询,即意味着该查询的二级缓存失效。
2.如果查询语句设置成false,即使用二级缓存,那么如果在数据库中修改了数据,而缓存数据还是原来的,这个时候就会出现脏读。
整合Ehcache
Ehcache是一个分布式缓存。
分布式缓存
系统为了提高性能,通常会对系统采用分布式部署(集群部署方式)
不使用分布式缓存,缓存的数据在各个服务单独存储,不方便开发。所以要使用分布式缓存对缓存数据进行集中式管理。
问题:Mybatis自身无法实现分布式缓存,需要和其它分布式缓存框架进行整合。
整合思路(重点)
Mybatis提供了一个cache接口,同时它自己有一个默认的实现类PerpetualCache。
通过实现cache接口可以实现mybatis缓存数据通过其他缓存数据库整合,mybatis的特长是sql,缓存数据管理不是mybatis的特长,为了提高mybatis的性能,所以需要mybatis和第三方缓存数据库整合,比如ehcache、memcache、redis等
整合ehcache的步骤
第一步:引入ehcache的jar包
第二步:在mapper映射文件中,配置cache标签的type为ehcache对cache接口的实现类类型
<!-- 开启二级缓存,默认使用的是PerpetualCache -->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
第三步:加入ehcache的配置文件ehcache.xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!-- 缓存数据要存放的磁盘地址 -->
<diskStore path="F:\develop\ehcache" />
<defaultCache maxElementsInMemory="1000"
maxElementsOnDisk="10000000" eternal="false" overflowToDisk="false"
timeToIdleSeconds="120" timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
应用场景
使用场景:对于访问响应速度要求高,但是实时性不高的查询,可以采用二级缓存技术。
注意:在使用二级缓存的时候,要设置一下刷新间隔(cache标签中有一个flashInterval属性)来定时刷新二级缓存,这个刷新间隔根据具体需求来设置,比如设置30分钟、60分钟等,单位为毫秒。
局限性
Mybatis二级缓存对细粒度的数据级别的缓存实现不好。
场景:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次查询都是最新的商品信息,此时如果使用二级缓存,就无法实现当一个商品发生变化只刷新该商品的缓存信息而不刷新其他商品缓存信息,因为二级缓存是mapper级别的,当一个商品的信息发送更新,所有的商品信息缓存数据都会清空。
解决此类问题,需要在业务层根据需要对数据有针对性的缓存。
比如可以对经常变化的数据操作单独放到另一个namespace的mapper中。