🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] ***** # 7.2 二级缓存 MyBatis的二级缓存在于 SqlSessionFactory 的 生命周期中。 当存在多个SqlSessionFactory时, 它们的缓存都是绑定在各自对象上的, 缓存数据在一般情况下是不相通的。 只有在使用如 Redis这样的缓存数据库时, 才可以共享缓存。 ## **7.2.1 配置二级缓存** **全局配置** 在mybatis-config.xml中, 这个参数值默认为 true, 可以不必配置。 ![](https://box.kancloud.cn/751b7875e5e3cc84ae0568b4e658c02e_589x122.png) ***** MyBatis的二级缓存是和命名空间绑定的, 即二级缓存需要配置在Mapper.xml映射文件中或者配置在 Mapper.java 接口中。 * 在XML中, 命名空间就是 XML 根节点 mapper 的namespace属性。 * 在Mapper接 口中, 命名空间就是接口的全限定名称。 ### **7.2.1.1 Mapper.xml中配置二级缓存** 在RoleMapper.xml 开启二级缓存,加cache标签 ![](https://box.kancloud.cn/fcb0c70430819e26833f097dd0b5ab4d_721x256.png) #### **默认的二级缓存会有如下效果** * 映射语句文件中的所有SELECT语句将会被缓存。 * 映射语句文件中的所有INSERT、 UPDATE、 DELETE语句**会刷新二级缓存**。 * *缓存会使用Least Recently Used(LRU, 最近最少使用的) 算法来收回。* * *根据时间表(如no Flush Interval, 没有刷新间隔) ,缓存不会以任何时间顺序来刷新。* * 缓存会存储集合或对象(无论查询方法返回什么类型的值) 的1024个引用。 * 缓存会被视为read/write(可读/可写)的,对象不是共享的, 可以安全地被调用者修改, 并且不干扰其他调用者或线程所做的修改 **二级缓存属性配置示例** ![](https://box.kancloud.cn/d162b5ea9eb6d84408a64abdc5419936_363x189.png) * 创建了一个FIFO缓存 * 每隔60秒刷新一次 * 存储集合或对象的512个引用 * 返回的对象被认为是只读的, 在不同线程中的调用者之间修改它们会导致冲突。 ***** #### **cache可以配置的属性** **eviction(收回策略)** * LRU(最近最少使用的) : 移除最长时间不被使用的对象, 这是默认值。 * FIFO(先进先出) : 按对象进入缓存的顺序来移除它们。 * *SOFT(软引用) : 移除基于垃圾回收器状态和软引用规则的对象。* * *WEAK(弱引用) : 更积极地移除基于垃圾收集器状态和弱引用规则的对象。* **flushInterval(刷新间隔)** 。 可以被设置为任意的正整数, 而且它们代表一个合理的毫秒形式的时间段。 默认情况不设置, 没有刷新间隔, 缓存仅仅在调用语句(insert,update,delete)时刷新。 **size(引用数目**) 。 可以被设置为任意正整数, 要记住缓存的对象数目和运行环境的可用内存资源数目。 默认值是1024。 **readOnly(只读)** * 只读,true。给调用者返回缓存对象的相同实例。性能有优势,对象不能被修改。 * 可读写,false。通过序列化返回缓存对象的拷贝(不同的实例)。 慢, 但安全。 ## **7.2.2 使用二级缓存** * MyBatis使用SerializedCache(org.apache.ibatis.cache.decorators.SerializedCache)序列化缓存来实现可读写缓存类, 并通过序列化和反序列化来通过缓存获取数据时, 得到的是一个新的实例。 * 因为使用可读写缓存,可以使用SerializedCache序列化缓存,这个缓存类要求所有被序列化的对象必须实现Serializable(java.io.Serializable) 接口, 所以还需要修改SysRole对象 ![](https://box.kancloud.cn/76b400762dfd9bf32eb4ed2f2dd5eda9_794x266.png) **在CacheTest.java中测试二级缓存** ``` @Test public void testL2Cache(){ //获取 sqlSession SqlSession sqlSession = getSqlSession(); SysRole role1 = null; try { //获取 RoleMapper 接口 RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class); //调用 selectById 方法,查询 id = 1 的用户 role1 = roleMapper.selectById(1l); //对当前获取的对象重新赋值 role1.setRoleName("New Name"); //再次查询获取 id 相同的用户 SysRole role2 = roleMapper.selectById(1l); //虽然我们没有更新数据库,但是这个用户名和我们 role1 重新赋值的名字相同了 Assert.assertEquals("New Name", role2.getRoleName()); //不仅如何,role2 和 role1 完全就是同一个实例 因为此时默认使用一级缓存,返回同一个实例 Assert.assertEquals(role1, role2); } finally { //关闭当前的 sqlSession sqlSession.close(); } System.out.println("开启新的 sqlSession"); //开始另一个新的 session sqlSession = getSqlSession(); try { //获取 RoleMapper 接口 RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class); //调用 selectById 方法,查询 id = 1 的用户 SysRole role2 = roleMapper.selectById(1l); //第二个 session 获取的用户名仍然是 New Name Assert.assertEquals("New Name", role2.getRoleName()); //这里的 role2 和 前一个 session 查询的结果是两个不同的实例 Assert.assertNotEquals(role1, role2); //获取 role3 SysRole role3 = roleMapper.selectById(1l); //这里的 role2 和 role3 是两个不同的实例 Assert.assertNotEquals(role2, role3); } finally { //关闭 sqlSession sqlSession.close(); } } ``` 在第一个try块中使用的是一级缓存, 所以role1和role2是同一个实例。 在第二个try块中,当调用close方法关闭SqlSession时, SqlSession才会保存查询数据到二级缓存中。 在这之后二级缓存才有了缓存数据。role2和 role3都是反序列化得到的结果, 所以它们不是相同的实例。 在这一部分, 这两个实例是读写安全的, 其属性不会互相影响。 **只读缓存** 配置为只读缓存,MyBatis就会使用Map来存储缓存值, 从缓存中获取的对象就是同一个实例。 #### **注意** 在这个例子中并没有真正的读写安全, 为什么?因为这个测试中加入了一段不该有的代码, 即role1.setRoleName("New Name"); , 这里修改role1的属性值后, 按照常理应该更新数据, 更新后会清空一、 二级缓存, 这样就可以避免人 为产生的脏数(数据库里的roleName和缓存中的roleName不一样), 避免缓存和数据库的数据不一致。 所以想要安全使用, 需要避免毫无意义的修改。 #### **总结** **一级缓存:** 当一个SqlSession结束后该SqlSession中的一级缓存也就不存在了。 **二级缓存:** 是mapper级别的缓存。使用二级缓存时,多个SqlSession使用同一个Mapper的sql语句去操作数据库,得到的数据会存在二级缓存区域,它同样是使用HashMap进行数据存储。相比一级缓存SqlSession,二级缓存的范围更大,多个Sqlsession可以共用二级缓存,二级缓存是跨SqlSession的。