(十)mybatis之缓存
一、缓存的意义
-
将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)去查询,从缓存中进行查询,从而提高查询效率,解决了高并发系统的性能问题。
二、mybatis持久层缓存
- mybatis一级缓存是一个Sqlsession级别,SqlSession只能访问自己的一级缓存数据,二级缓存是跨SqlSession的,是mapper级别的缓存,对于mapper级别的缓存 不同的SqlSession是可以共享的。
三、一级缓存
3.1 原理
- 流程: 第一次发出sql语句查询id为1的用户时,sql查询的结果写入sqlSession一级缓存中,缓存使用的数据结构是一个map<key,value>
key: hashcode+sql+sql输入参数+sql输出参数 (作为SQL的唯一标识)
value: 用户信息。
同一个sqlSession再次发出相同的sql,就从缓存中取数据,不再查询数据库。如果两次中间出现commit操作(修改、添加、删除),此SqlSession中的一级缓存数据全部清空,下次再去缓存中就会查询不到数据再从数据库查询,从数据库查询到后再写入缓存。
每次查询都先从缓存进行查询。
注意:清空时会清空当前sqlsession缓存区域中的全部内容。
3.2 一级缓存配置
- mybatis默认支持一级缓存不需要配置
- 注意:mybatis和Spring整合后进行mapper代理开发,不支持一级缓存,mybatis和Spring整合,Spring按照mapper的模板去生成mapper代理对象,模板中在最后统一关闭sqlsession。
四、二级缓存
4.1 二级缓存原理
- 二级缓存的范围是mapper级别(mapper同一个命名空间),mapper以命名空间为单位创建缓存数据结构,结构为map<key,value>
每次查询先看是否开启二级缓存,如果开启先从二级缓存中数据结构中取缓存数据。
如果从二级缓存中没有取到,再从一级缓存中进行查找,如果一级缓存也没有,从数据库查询。
4.2 mybatis二级缓存配置
- mybaits的二级缓存是mapper范围级别,除了在SqlMapConfig.xml设置二级缓存的总开关,即:<setting name="cacheEnabled" value="true"/>
,还要在具体的mapper.xml中开启二级缓存,即:
<mapper namespace="com.shyroke.mapper.OrderMapper"> <cache /> </mapper>
4.3 注意
- mybatis二级缓存需要将查询结果映射的pojo实现 java.io.serializable接口,如果不实现则抛出异常:org.apache.ibatis.cache.CacheException: Error serializing object. Cause: java.io.NotSerializableException
- 二级缓存可以将内存中的数据写到硬盘,存在对象的序列和反序列化,所以要实现java.io.serializable接口。如果结果映射的pojo中还关联了别的pojo,那么它和它所关联的pojo也都要实现java.io.serializable接口。
4.4 二级缓存的禁用
- 对于变化频率较高的sql,需要禁用二级缓存: 在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。
<select id="findOrderListResultMap" resultMap="ordersUserMap" useCache="false">
4.5 刷新缓存
- 如果SqlSession操作commit操作,对二级缓存进行刷新(全局清空)
设置与commit相关statement(insert,update,delete等标签)的flushCache属性是否刷新缓存,默认值是true,如果设置了为false,即使后台数据库发生了变化,只要缓存没有过期,也只会读取缓存中数据 就会获取不到最新数据。
4.6 测试
public static void main(String[] args) throws IOException { String resource = "resource/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session = sessionFactory.openSession(); SqlSession session2 = sessionFactory.openSession(); OrderMapper orderMapper = session.getMapper(OrderMapper.class); OrderMapper orderMapper2 = session2.getMapper(OrderMapper.class); Customer customer=orderMapper.findCustomerById(1); System.out.println(customer); //关闭session,将信息写入二级缓存中 session.close(); Customer customer2=orderMapper2.findCustomerById(1); System.out.println(customer2); }
- 注意:只有关闭sqlSession的时候,才会把信息写入二级缓存中。
4.7 mybatis自带的cache参数属性(了解)
mybatis的cache参数只适用于mybatis维护缓存。
- flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。 size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是1024。
readOnly(只读)属性可以被设置为true或false。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。
如下例子:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会导致冲突。可用的收回策略有, 默认的是 LRU:
LRU – 最近最少使用的:移除最长时间不被使用的对象。
FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
五、番外
-
二级缓存的应用场景
对于查询频率高,变化频率低的数据建议使用二级缓存。
对于访问多的查询请求且用户对查询结果的实时性要求不高,此时可采用mybatis二级缓存技术降低数据库的访问量,提高访问速度,业务场景比如:耗时较高的统计分析sql、电话账单查询sql等。
实现方法如下:通过设置刷新时间间隔,由mybatis每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新时间间隔flushInterval,比如设置30分钟、60分钟、24小时等,根据需求而定。
mybatis二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存。