MyBatis-23MyBatis缓存配置【二级缓存】
文章目录
- 概述
- 二级缓存的配置
- 全局开关cacheEnabled
- Mapper.xml中配置二级缓存
- Mapper接口中配置二级缓存
- 只使用注解方式配置二级缓存
- 同时使用注解方式和XML映射文件时
- 二级缓存的使用
- 前提:实体类实现Serializable接口
- 示例
- 实体类SysPrivilege实现Serializable接口
- PrivilegeMapper接口类增加接口方法
- PrivilegeMapper.xml中配置对单表操作的SQL
- 单元测试
- 注意事项(重要)
- MyBatis二级缓存的使用场景
- 避免使用二级缓存
- 为什么避免使用二级缓存
- 多表操作一定不能使用缓存
- 如何挽救二级缓存
概述
MyBatis-22MyBatis缓存配置【一级缓存】 中介绍了MyBatis默认的一级缓存,了解即可。
这里我们来看下工作中最常用的二级缓存。
MyBaits的二级缓存可以理解为存在于SqlSessionFactory的生命周期中。
目前还没接触过同时存在多个SqlSessionFactory的情况,但可以知道当存在多个SqlSessionFactory时,他们的缓存对象都是绑定在各自对象上的,缓存数据在一般情况下是不相通的。 只有在使用如redis这样的缓存数据库时,才可以共享缓存。
二级缓存的配置
全局开关cacheEnabled
在MyBatis的全局配置settings中有一个参数 cacheEnabled , 这个参数是二级缓存的全局开关,默认为true,初始状态为启用状态。 如果设置为false ,即使后面的二级缓存配置,也不会生效。 默认为true,所以不用配置。
MyBatis的二级缓存是和命名空间绑定的,即二级缓存需要配置在Mapper.xml映射文件中或者配置在Mapper.java接口中。 在映射文件中,命名空间就是XML根节点mapper的namespace属性。 在Mapper接口中,命名空间就是接口的全限定名称。
Mapper.xml中配置二级缓存
在保证二级缓存全局配置开启的情况下,如果想要给PrivilegeMapper.xml开启二级缓存只需要在PrivilegeMapper.xml中添加 <cache/>
元素即可。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" ><!-- 当Mapper接口和XML文件关联的时候, namespace的值就需要配置成接口的全限定名称 -->
<mapper namespace="com.artisan.mybatis.xml.mapper.PrivilegeMapper"><cache/><!-- 其他配置 -->
</mapper>
默认的二级缓存功能如下:
映射语句文件中所有的select语句将会被缓存
映射语句文件中所有的insert update delete 语句会刷新缓存
缓存会使用(Least Flush Interval,LRU最近最少使用的)算法来收回
根据时间表(如 no Flush Interval,没有刷新间隔),缓存不会以任何时间顺序来刷新
缓存会存储集合或对象(无论查询方法返回什么类型的值)的1024个引用
缓存会被视为read/wriete(可读/可写)的,意味着对象检索不是共享的,而且可以安全的被调用者修改,而不干扰其他调用者或者线程所做的潜在修改。
所有的这些属性都是可以通过缓存元素的属性来修改,比如
<cacheeviction="FIFO"flushInterval="60000"size="512"readOnly="true"/>
这个更高级的配置创建了一个FIFP缓存,每隔60S刷新一次,存储集合或对象的512个引用,而且返回的对象被认为是只读的,因而在不同线程中的调用者之间修改它们会导致冲突。
cache可以配置的属性如下:
- eviction(收回策略)
- LRU 最近最少使用的,移除最长时间不被使用的对象,这是默认值
- FIFO 先进先出,按对象进入缓存的顺序来移除它们
- SOFT 软引用,移除基于垃圾回收器状态和软引用规则的对象
- WEAK 弱引用,更积极的移除基于垃圾收集器状态和弱引用规则的对象
flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。 默认情况不设置,即没有刷新间隔,缓存仅仅在调用语句时刷新
size(引用数目)可以被设置为任意的正整数,要记住缓存的对象数目和运行环境的可用内存资源数目,默认1024
readOnly(只读)属性可以被设置为true后者false。 只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,这提供了很重要的性能优势。 可读写的缓存会通过序列化返回缓存对象的拷贝,这种方式会慢一些,但很安全,因此默认为false
Mapper接口中配置二级缓存
使用注解的方式时,如果想对注解方式启用二级缓存,还需要在Mapper接口中进行配置,如果Mapper接口也存在对应的XML映射文件,两者同时开启缓存时,还需要特殊配置。
只使用注解方式配置二级缓存
当只使用注解方式配置二级缓存时,比如RoleMapper接口中,则需要增加如下配置
@CacheNamespace
public interface RoleMapper {....
}
只需要增加@CacheNamespace(org.apache.ibatis.annotations.CacheNamespace),该注解同样可以配置各项属性
@CacheNamespace{eviction = FifoCache.class,flushInterval = 60000,size = 512,readWrite = true
}
这里的readWrite属性和XML中的readOnly属性一样,用于配置缓存是否为只读类型,在这里true为读写,false为只读,默认为true。
同时使用注解方式和XML映射文件时
当同时使用注解方式和XML映射文件时,如果同时配置了上述的二级缓存(使用xml 以及接口上标注了@CacheNamespace),会抛出如下异常
Cache collection already contains value for **
这是因为Mapper接口和对应的XML文件是相同的命名空间,想使用二级缓存,两者必须同时配置(如果接口不存在使用注解方式的方法,可以只在XML中配置),因此按照上面的方式进行配置就出错,这个时候应该使用参照缓存,
在Mapper接口中,参照缓存配置如下
@CacheNamespaceRef(RoleMapper.class)
public interface RoleMapper{}
MyBatis很少会同时使用Mapper接口注解方式和XML映射文件,所以参照缓存并不是为了解决这个问题而设计的,参照缓存主要是为了解决脏读
二级缓存的使用
前提:实体类实现Serializable接口
由于MyBatis配置的是可读写的缓存,而MyBatis使用SerializedCache序列化缓存来实现可读写缓存类,并通过序列化和反序列化来保证通过缓存获取数据时,得到的是一个新的示例。 因此如果配置为只读缓存,MyBatis会使用Map来存储缓存值,这种情况下,从缓存中获取的对象就是同一个实例。
因为使用可读写缓存,可以使用SerializedCache序列化缓存,这个缓存类要求所有被序列化的对象必须实现java.io.Serializable接口,所以需要修改SysPrivilege实体类
public class SysPrivilege implements Serializable {private static final long serialVersionUID = 6315662516417216377L;// 其他保持不变
}
示例
实体类SysPrivilege实现Serializable接口
public class SysPrivilege implements Serializable {private static final long serialVersionUID = 6315662516417216377L;// 其他保持不变
}
PrivilegeMapper接口类增加接口方法
/*** * * @Title: selectPrivilegeByIdWithCache* * @Description: 二级缓存测试方法 ,实体类SysPrivilege必须要实现Serializable* * @param id* @return* * @return: SysPrivilege*/SysPrivilege selectPrivilegeByIdWithCache(Long id);
PrivilegeMapper.xml中配置对单表操作的SQL
<select id="selectPrivilegeByIdWithCache" resultType="com.artisan.mybatis.xml.domain.SysPrivilege">SELECTid,privilege_name privilegeName,privilege_url privilegeUrlFROMsys_privilegeWHEREid = #{id}</select>
单元测试
@Testpublic void selectPrivilegeByIdWithCacheTest() {logger.info("selectPrivilegeByIdWithCacheTest");SqlSession sqlSession = getSqlSession();SysPrivilege sysPrivilege = null;try {// 获取接口PrivilegeMapper privilegeMapper = sqlSession.getMapper(PrivilegeMapper.class);// 调用接口方法sysPrivilege = privilegeMapper.selectPrivilegeByIdWithCache(1L);sysPrivilege.setPrivilegeName("New Priv");// 再次调用相同的接口方法,查询相同的用户logger.info("再次调用相同的接口方法,查询相同的用户 Begin");SysPrivilege sysPrivilege2 = privilegeMapper.selectPrivilegeByIdWithCache(1L);logger.info("再次调用相同的接口方法,查询相同的用户 End");// 一级缓存在同一个sqlSession中,虽然没有更新数据库,但是会使用一级缓存Assert.assertEquals("New Priv", sysPrivilege2.getPrivilegeName());// sysPrivilege 和 sysPrivilege2 是同一个实例Assert.assertEquals(sysPrivilege, sysPrivilege2);} finally {// sqlSession关闭后,在二级缓存开启的前提下,会写入二级缓存sqlSession.close();}logger.info("重新获取一个SqlSession");sqlSession = getSqlSession();try {// 获取接口PrivilegeMapper privilegeMapper = sqlSession.getMapper(PrivilegeMapper.class);// 调用接口方法SysPrivilege sysPrivilege2 = privilegeMapper.selectPrivilegeByIdWithCache(1L);sysPrivilege.setPrivilegeName("New Priv");// 第二个session获取的权限名为 New PrivAssert.assertEquals("New Priv", sysPrivilege2.getPrivilegeName());// 这里的sysPrivilege2 和 前一个session中的sysPrivilege不是同一个实例Assert.assertNotEquals(sysPrivilege, sysPrivilege2);// 获取sysPrivilege3SysPrivilege sysPrivilege3 = privilegeMapper.selectPrivilegeByIdWithCache(1L);// 这里的sysPrivilege2 和sysPrivilege3是两个不同的实例Assert.assertNotEquals(sysPrivilege2, sysPrivilege3);} finally {// sqlSession关闭后,在二级缓存开启的前提下,会写入二级缓存sqlSession.close();}}
日志
2018-05-07 14:21:22,949 INFO [main] (BaseMapperTest.java:26) - sessionFactory bulit successfully
2018-05-07 14:21:22,949 INFO [main] (BaseMapperTest.java:29) - reader close successfully
2018-05-07 14:21:22,960 INFO [main] (PrivilegeMapperTest.java:38) - selectPrivilegeByIdWithCacheTest
2018-05-07 14:21:23,010 DEBUG [main] (LoggingCache.java:62) - Cache Hit Ratio [com.artisan.mybatis.xml.mapper.PrivilegeMapper]: 0.0
2018-05-07 14:21:23,080 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Preparing: SELECT id, privilege_name privilegeName, privilege_url privilegeUrl FROM sys_privilege WHERE id = ?
2018-05-07 14:21:23,270 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Parameters: 1(Long)
2018-05-07 14:21:23,320 TRACE [main] (BaseJdbcLogger.java:151) - <== Columns: id, privilegeName, privilegeUrl
2018-05-07 14:21:23,320 TRACE [main] (BaseJdbcLogger.java:151) - <== Row: 1, 用户管理, /users
2018-05-07 14:21:23,340 DEBUG [main] (BaseJdbcLogger.java:145) - <== Total: 1
2018-05-07 14:21:23,340 INFO [main] (PrivilegeMapperTest.java:48) - 再次调用相同的接口方法,查询相同的用户 Begin
2018-05-07 14:21:23,340 DEBUG [main] (LoggingCache.java:62) - Cache Hit Ratio [com.artisan.mybatis.xml.mapper.PrivilegeMapper]: 0.0
2018-05-07 14:21:23,340 INFO [main] (PrivilegeMapperTest.java:50) - 再次调用相同的接口方法,查询相同的用户 End
2018-05-07 14:21:23,350 INFO [main] (PrivilegeMapperTest.java:60) - 重新获取一个SqlSession
2018-05-07 14:21:23,360 DEBUG [main] (LoggingCache.java:62) - Cache Hit Ratio [com.artisan.mybatis.xml.mapper.PrivilegeMapper]: 0.3333333333333333
2018-05-07 14:21:23,360 DEBUG [main] (LoggingCache.java:62) - Cache Hit Ratio [com.artisan.mybatis.xml.mapper.PrivilegeMapper]: 0.5
日志分析:
第一部分第一次和第二次查询的sysPrivilege 和 sysPrivilege2 是完全相同的实例,这是使用的是默认的一级缓存,所以返回同一个实例。
当调用close方法关闭sqlSession后,sqlSession才回保存查询数据到二级缓存中。 在这之后二级缓存才有了缓存数据。 所以第一次里看到的两次查询时,命中率都是0 。
重新开一个sqlSession,再次获取sysPrivilege 时,因为缓存中有了数据,没有查询数据库,而是输出了命中率,这是的命中率为 0.3333333333333333 , 查询3次,命中1次,因此是1/3
紧接着第4次查询,加上上次的命中,2/4 ,命中率为0.5
注意: 我们为了测试,在获取到数据后,调用了setPrivilegeName方法,实际中并不需要,避免人为产生脏数据。
注意事项(重要)
MyBatis二级缓存的使用场景
只能在【只有单表操作】的表上使用缓存,不只是要保证这个表在整个系统中只有单表操作,而且和该表有关的全部操作必须全部在一个namespace下。
在可以保证查询远远大于insert,update,delete操作的情况下使用缓存,这一点需要保证在1的前提下才可以!
避免使用二级缓存
二级缓存带来的好处远远比不上他所隐藏的危害。
二级缓存
- 缓存是以namespace为单位的,不同namespace下的操作互不影响。
- insert,update,delete操作会清空所在namespace下的全部缓存。
- 通常使用MyBatis Generator生成的代码中,都是各个表独立的,每个表都有自己的namespace。
为什么避免使用二级缓存
在符合【MyBatis二级缓存的使用场景】的要求时,并没有什么危害。
其他情况就会有很多危害了。
针对一个表的某些操作不在他独立的namespace下进行。
例如在UserMapper.xml中有大多数针对user表的操作。但是在一个XXXMapper.xml中,还有针对user单表的操作。
这会导致user在两个命名空间下的数据不一致。如果在UserMapper.xml中做了刷新缓存的操作,在XXXMapper.xml中缓存仍然有效,如果有针对user的单表查询,使用缓存的结果可能会不正确。
更危险的情况是在XXXMapper.xml做了insert,update,delete操作时,会导致UserMapper.xml中的各种操作充满未知和风险。
有关这样单表的操作可能不常见.
多表操作一定不能使用缓存
首先不管多表操作写到那个namespace下,都会存在某个表不在这个namespace下的情况。
例如两个表:role和user_role,如果想查询出某个用户的全部角色role,就一定会涉及到多表的操作。
<select id="selectUserRoles" resultType="UserRoleVO">select * from user_role a,role b where a.roleid = b.roleid and a.userid = #{userid}
</select>
像上面这个查询,你会写到那个xml中呢??
不管是写到RoleMapper.xml还是UserRoleMapper.xml,或者是一个独立的XxxMapper.xml中。如果使用了二级缓存,都会导致上面这个查询结果可能不正确。
如果你正好修改了这个用户的角色,上面这个查询使用缓存的时候结果就是错的。
这点应该很容易理解。
在我看来,就以MyBatis目前的缓存方式来看是无解的。多表操作根本不能缓存。
如果你让他们都使用同一个namespace(通过<cache-ref>
)来避免脏数据,那就失去了缓存的意义。
实际上就是说,二级缓存不能用
如何挽救二级缓存
想更高效率的使用二级缓存是解决不了的
建议放弃二级缓存,在业务层使用可控制的缓存代替更好。
更多注意事项请参考刘老师的博客深入了解MyBatis二级缓存
MyBatis-23MyBatis缓存配置【二级缓存】相关推荐
- 深入浅出 MyBatis 的一级、二级缓存机制
一.MyBatis 缓存 缓存就是内存中的数据,常常来自对数据库查询结果的保存.使用缓存,我们可以避免频繁与数据库进行交互,从而提高响应速度. MyBatis 也提供了对缓存的支持,分为一级缓存和二级 ...
- Mybatis一级缓存,二级缓存的实现就是这么简单
介绍 又到了一年面试季,所以打算写一点面试常问的东西,争取说的通俗易懂.面试高级岗,如果你说熟悉Mybatis,下面这些问题基本上都会问 Mybatis插件的实现原理? 如何写一个分页插件? Myba ...
- MyBatis框架:延迟加载策策略、一级缓存、二级缓存
MyBatis框架:延迟加载策略和缓存 Mybatis 延迟加载策略 1.1 何为延迟加载? 1.2 实现需求 1.3 使用association实现延迟加载 1.3.1 账户的持久层DAO接口 1. ...
- 浅谈Mybatis的一级缓存和二级缓存
MyBatis的缓存机制 缓存的引入 当我们大量执行重复的查询SQL语句的时候,会频繁的和数据库进行通信,会增加查询时间等影响用户体验的问题,可以通过缓存,以降低网络流量,使网站加载速度更快. MyB ...
- MyBatis 缓存详解-第三方缓存做二级缓存
除了MyBatis 自带的二级缓存之外,我们也可以通过实现Cache 接口来自定义二级缓存. MyBatis 官方提供了一些第三方缓存集成方式,比如ehcache 和redis: https://gi ...
- mybatis注解开发使用二级缓存
在SqlMapConfig中开启二级缓存支持 <!--配置开启二级缓存--><settings><setting name="cacheEnabled" ...
- MyBatis】MyBatis一级缓存和二级缓存
转载自 MyBatis]MyBatis一级缓存和二级缓存 MyBatis自带的缓存有一级缓存和二级缓存 一级缓存 Mybatis的一级缓存是指Session缓存.一级缓存的作用域默认是一个SqlSe ...
- Mybatis 详解--- 一级缓存、二级缓存
2019独角兽企业重金招聘Python工程师标准>>> Mybatis 为我们提供了一级缓存和二级缓存,可以通过下图来理解: ①.一级缓存是SqlSession级别的缓存.在操作数据 ...
- Mybatis源码分析之(七)Mybatis一级缓存和二级缓存的实现
文章目录 一级缓存 二级缓存 总结 对于一名程序员,缓存真的很重要,而且缓存真的是老生常谈的一个话题拉.因为它在我们的开发过程中真的是无处不在.今天LZ带大家来看一下.Mybatis是怎么实现一级缓存 ...
- 框架源码专题:Mybatis的一级缓存、二级缓存是什么?有什么作用?
文章目录 1. Mybatis中缓存的作用 2. 一级缓存 3. 二级缓存 4. 一级缓存和二级缓存的区别 5. 通过代码观察Mybatis缓存工作的全过程 1. Mybatis中缓存的作用 首先缓存 ...
最新文章
- 产品经理的高阶能力:商业思维基于商业画布的研习方法论
- JS中关于能不能加分号
- ldd /usr/bin/mysql_mysql客户端登录时报mysql: relocation error错误
- vb net的定时循环_.NET工具ReSharper:如何帮助Visual Studio用户?
- android 图片转base64内存变大了_开发者最喜爱的图片编码格式:opencv编码,解码,显示base64图片...
- 敏捷开发系列学习总结(12)——给Scrum Master的十个建议,你值得拥有
- 头文件和实现文件的关系
- 面向客户定制化开发项目的管理重点
- 期末作业代码网页设计代码——花店购物网站源码(27页) 学生动花店购物页设计模板下载 植物大学生HTML网页制作作品 简单网页设计成品 dreamweaver学生网站模板
- 39、VS838红外线接收实验
- cvtColor +内存泄漏
- DAS、NAS、SAN三种高端存储技术分析
- andriod中3g模块没有mac地址的原因
- redis统计用户日活量_使用redis统计用户日活、月活(实践版)
- Unity Shader UV动画之高光材质加上透明材质与UV动画
- 【python 淘宝爬虫】python 淘宝店铺名称,旺旺,销售量 抓取
- 水晶报表打印纸张设置
- Some NCCL operations have failed or timed out.
- 本地搭建 Bootlin elixir 查阅内核代码
- Vue + Element 实现导入导出Excel
热门文章
- mysql custom_MySQL安装教程
- 3400g主机用linux系统,最强整合平台!锐龙5 3400G小钢炮主机配置推荐
- This tutorial code needs the xfeatures2d contrib module to be run.
- pyspark rdd 数据持久化
- 论文笔记:Autoregressive Tensor Factorizationfor Spatio-temporal Predictions
- 淘商们用数据精细化分析客户群体
- 面向对象编程风格基于对象编程风格
- Matlab分布式和并行编程
- 机器学习第1天:数据预处理
- GPU高效通信算法-Ring Allreduce