mybatis缓存
概述
MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。
MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存。
- 默认情况下,只有一级缓存(SqlSession级别的缓存, 也称为本地缓存)开启。
- 二级缓存需要手动开启和配置,他是基于namespace级 别的缓存。
- 为了提高扩展性。MyBatis定义了缓存接口Cache。我们 可以通过实现Cache接口来自定义二级缓存
一级缓存
又叫本地缓存,一次sqlSession会话级别的缓存。一级缓存是一直开启的,缓存结构其实是SqlSession级别的一个Map。
与数据库同一次会话期间查询到的数据会放在本地缓存中,以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库。
一级缓存失效(没有使用到当前一级缓存的情况,效果就是,还需要再向数据库发出查询)的情况:
- sqlSession不同
- sqlSession相同,查询条件不同.(当前一级缓存中还没有这个数据)
- sqlSession相同,两次查询之间执行了增删改操作(mybatis认为这次增删改可能对当前数据有影响所以会删除缓存)
- sqlSession相同,手动清除了一级缓存(缓存清空)
二级缓存
又叫全局缓存,基于namespace级别的缓存,一个namespace对应一个二级缓存。
工作机制
- 一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中;
- 如果会话关闭;一级缓存中的数据会被保存到二级缓存中;新的会话查询信息,就可以参照二级缓存中的内容;
- 不同namespace查出的数据会放在自己对应的缓存中(map)
效果:数据会从二级缓存中获取,查出的数据都会被默认先放在一级缓存中。只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中
使用
-
开启全局二级缓存配置
1 2 3 4
<settings> <!--显式的指定每个我们需要更改的配置的值,即使他是默认的。防止版本更新带来的问题 --> <setting name="cacheEnabled" value="true"/> </settings>
-
去mapper.xml中配置使用二级缓存:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
<!-- eviction:缓存的回收策略: • LRU – 最近最少使用的:移除最长时间不被使用的对象。 • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。 • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。 • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。 • 默认的是 LRU。 flushInterval:缓存刷新间隔 缓存多长时间清空一次,默认不清空,设置一个毫秒值 readOnly:是否只读: true:只读;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据,不需要实现序列化 mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,速度快 false:非只读:mybatis觉得获取的数据可能会被修改。 mybatis会利用序列化&反序列的技术克隆一份新的数据给你。安全,速度慢 size:缓存存放多少元素; type="":指定自定义缓存的全类名; 实现Cache接口即可;eg:org.mybatis.caches.ehcache.EhcacheCache --> <cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"/>
-
如果readOnly为false,我们的POJO需要实现序列化接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
package com.eh.eden.mybatis.orm.bean; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; import java.io.Serializable; @Data @NoArgsConstructor @AllArgsConstructor @ToString public class Employee implements Serializable { private static final long serialVersionUID = 5591903708394742480L; private Integer id; private String lastName; private String gender; private String email; private Dept dept; }
-
测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
@Test public void test4() throws IOException { SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(); // 第一次会话 try (SqlSession session = sqlSessionFactory.openSession(true) ) { EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class); Employee employee = employeeMapper.getEmployeeById(1); System.out.println(employee); } // 第二次会话 try (SqlSession session = sqlSessionFactory.openSession(true) ) { EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class); Employee employee = employeeMapper.getEmployeeById(1); System.out.println(employee); } }
测试结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
20201009 21:33:06 [main] DEBUG com.eh.eden.mybatis.orm.dao.EmployeeMapper - Cache Hit Ratio [com.eh.eden.mybatis.orm.dao.EmployeeMapper]: 0.0 20201009 21:33:06 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection 20201009 21:33:06 [main] DEBUG o.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 1789718525. 20201009 21:33:06 [main] DEBUG c.e.e.m.orm.dao.EmployeeMapper.getEmployeeById - ==> Preparing: select id, last_name, gender, email from tbl_employee where id = ? 20201009 21:33:06 [main] DEBUG c.e.e.m.orm.dao.EmployeeMapper.getEmployeeById - ==> Parameters: 1(Integer) 20201009 21:33:06 [main] TRACE c.e.e.m.orm.dao.EmployeeMapper.getEmployeeById - <== Columns: id, last_name, gender, email 20201009 21:33:06 [main] TRACE c.e.e.m.orm.dao.EmployeeMapper.getEmployeeById - <== Row: 1, 王五, 1, w5@gmail.com 20201009 21:33:06 [main] DEBUG c.e.e.m.orm.dao.EmployeeMapper.getEmployeeById - <== Total: 1 Employee(id=1, lastName=null, gender=1, email=w5@gmail.com, dept=null) 20201009 21:33:06 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@6aaceffd] 20201009 21:33:06 [main] DEBUG o.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 1789718525 to pool. 20201009 21:33:06 [main] WARN org.apache.ibatis.io.SerialFilterChecker - As you are using functionality that deserializes object streams, it is recommended to define the JEP-290 serial filter. Please refer to https://docs.oracle.com/pls/topic/lookup?ctx=javase15&id=GUID-8296D8E8-2B93-4B9A-856E-0A65AF9B8C66 20201009 21:33:06 [main] DEBUG com.eh.eden.mybatis.orm.dao.EmployeeMapper - Cache Hit Ratio [com.eh.eden.mybatis.orm.dao.EmployeeMapper]: 0.5 Employee(id=1, lastName=null, gender=1, email=w5@gmail.com, dept=null)
可以看到第二次会话查询命中了缓存,缓存命中率0.5。
缓存有关设置
-
全局setting的cacheEnable
配置二级缓存的开关。一级缓存一直是打开的。
-
select标签的useCache属性
配置这个select是否使用二级缓存。一级缓存一直是使用的
-
sql标签的flushCache属性
增删改默认flushCache=true。sql执行以后,会同时 查询默认flushCache=false。
-
sqlSession.clearCache()
只是用来清除一级缓存。
-
localCacheScope 本地缓存作用域,一级缓存
- SESSION,当前会话的所有数据保存在会话缓存中
- STATEMENT:没有数据,可以达到禁用一级缓存的目的;
缓存工作流程图
第三方缓存整合
EhCache 是一个纯Java的进程内缓存框架,具有快速、精 干等特点,是Hibernate中默认的CacheProvider。
MyBatis定义了Cache接口方便我们进行自定义扩展。步骤如下:
-
导入ehcache包,以及整合包,日志包
ehcache-core-2.6.8.jar,slf4j-api-1.6.1.jar、slf4j-log4j12-1.6.2.jar,mybatis-ehcache-1.0.3.jar(适配器jar)
也可以直接添加ehcache依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
<!-- 添加Ehcache核心包 --> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache-core</artifactId> <version>2.6.10</version> </dependency> <!-- mybatis整合Ehcache的适配器 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.0.0</version> </dependency> <!-- 引入日志包 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.12</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.6</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
-
编写ehcache.xml配置文件
注意设置overflowToDisk=true,否则观看效果不明显。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
<?xml version="1.0" encoding="UTF-8"?> <!-- http://www.ehcache.org/ehcache.xsd 访问不了改成使用迅雷下载到本地直接引用 --> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true" monitoring="autodetect" dynamicConfig="true"> <!-- 指定数据在磁盘中的存储位置 --> <diskStore path="/tmp/com.eh/ehcache"/> <!-- 缓存策略 --> <defaultCache maxElementsInMemory="1000" maxElementsOnDisk="10000000" eternal="false" overflowToDisk="true" timeToIdleSeconds="120" timeToLiveSeconds="120" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> </defaultCache> </ehcache> <!-- name:Cache的唯一标识 maxElementsInMemory:内存中最大缓存对象数 maxElementsOnDisk:磁盘中最大缓存对象数,若是0表示无穷大 eternal:Element是否永久有效,一但设置了,timeout将不起作用 overflowToDisk:配置此属性,当内存中Element数量达到maxElementsInMemory时,Ehcache将会Element写到磁盘中 timeToIdleSeconds:设置Element在失效前的允许闲置时间。仅当element不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大 timeToLiveSeconds:设置Element在失效前允许存活时间。最大时间介于创建时间和失效时间之间。仅当element不是永久有效时使用,默认是0.,也就是element存活时间无穷大 diskPersistent:是否缓存虚拟机重启期数据 diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒 diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区 memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用) -->
-
配置cache标签
1 2 3 4 5 6 7 8
<!-- ehcache配置项 --> <cache type="org.mybatis.caches.ehcache.LoggingEhcache" > <property name="timeToIdleSeconds" value="3600"/><!--1 hour--> <property name="timeToLiveSeconds" value="3600"/><!--1 hour--> <property name="maxEntriesLocalHeap" value="1000"/> <property name="maxEntriesLocalDisk" value="10000000"/> <property name="memoryStoreEvictionPolicy" value="LRU"/> </cache>
有部分属性在ehcache.xml里面配置过,则mapper文件中的属性会覆盖掉ehcache.xml里面的属性值。同样,也可以使用一种偷懒的方式,直接使用一行,这里其余的属性则都使用ehcache.xml里面的默认属性了。
1 2
<!-- ehcache配置项 --> <cache type="org.mybatis.caches.ehcache.LoggingEhcache" />
参照缓存:若想在命名空间中共享相同的缓存配置和实例。 可以使用 cache-ref 元素来引用另外一个缓存。
|
|
测试:
|
|
输出结果:
|
|
可以看到ehcache有将对象放入内存缓存中,也可以看磁盘情况:
|
|
可以看到数据写到磁盘中了。
总结:
- mybatis的二级缓存的范围是命名空间(namespace)
- 只要这个命名空间下有一个 insert、update、delete mybatis 就会把这个命名空间下的二级缓清空。
- 如果同一个sql在不同的命名空间下,就会出现脏数据,因为一个insert、update、deleted 了另一个可能还使用着缓存数据,这样就会出现数据的不一致性。
- 如果更新、删除、插入的频率比较高的话,就会删除所有缓存在添加所有缓存在删除,这样缓存的命中率很低或者说根本就起不到缓存作用而且会消耗资源。
所以在没解决这个问题的前提下,还是不提倡使用二级缓存。