MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。
• MyBatis系统中默认定义了两级缓存:
• 一级 缓存和 二级缓存。
– 1、默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启。
– 2、二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
– 3、为了提高扩展性。MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
1、一级缓存
1.1 概述
一级缓存也称为本地缓存:sqlSession级别的缓存,与数据库同一次会话期间(同一个SqlSession)查询到的数据会放在本地缓存中。以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库。当 SqlSession flush 或 close 后, 该SqlSession 中的所有 Cache 将被清空。
一级缓存是一直开启的(无法关闭);但可以调用 clearCache()来清空本地缓存, 或者改变缓存的作用域。
一级缓存的实现原理就是:SqlSession级别的一个Map,把查询到的数据放到Map中,下次的查询就先从Map里面找,没有找到才去查询数据库。
在mybatis3.1之后, 可以配置本地缓存的作用域,在 全局配置文件mybatis-config.xml 中配置:
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
localCacheScope | MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。 | SESSION | STATEMENT | SESSION |
1.2 演示一级缓存
1.2.1 映射文件sql
<select id="selectById" resultType="com.mybatis.pojo.Employee">
select id,last_name,age,addr from t_employee where id = #{id}
</select>
1.2.2 mapper接口
public Employee selectById(String id);
1.2.3 测试查看打印日志
@Test
public void test2(){
SqlSession sqlSession = null;
try {
sqlSession = sessionFactory.openSession();
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
Employee employee = employeeMapper.selectById("1");//第一次查询,肯定走数据库
System.out.println(employee);
Employee employee2 = employeeMapper.selectById("1");//第二次查询不会发送sql,走的缓存
System.out.println(employee2);
}finally {
sqlSession.close();
}
}
DEBUG 02-20 15:16:04,617 ==> Preparing: select id,last_name,age,addr from t_employee where id = ? (BaseJdbcLogger.java:145)
DEBUG 02-20 15:16:04,737 ==> Parameters: 1(String) (BaseJdbcLogger.java:145)
DEBUG 02-20 15:16:04,764 <== Total: 1 (BaseJdbcLogger.java:145)
Employee{id='1', lastName='tom', age=22, addr='changshashi'}
Employee{id='1', lastName='tom', age=22, addr='changshashi'}
可以看到,第二查询的时候,并没有发送sql,而是从缓存中取出的数据。
1.3 一级缓存失效的四种情况
• 同一次会话期间只要查询过的数据都会保存在当前SqlSession的一个Map中。
• key:hashCode+查询的SqlId+编写的sql查询语句+参数
一级缓存失效的四种情况
– 1、sqlSession不同:不同的SqlSession对应不同的一级缓存;
– 2、sqlSession相同:查询条件不同(当前一级缓存中还没有这个数据)
– 3、sqlSession相同:两次查询期间执行了任何一次增删改操作(因为这次增删改可能对当前数据有影响)
– 4、sqlSession相同:两次查询期间手动清空了缓存(openSession.clearCache())
2、二级缓存
2.1 概述
二级缓存也称作全局缓存,基于namespace级别的缓存,一个namespace对应一个二级缓存。
工作机制:
1、一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中;
2、如果会话关闭;一级缓存中的数据会被保存到二级缓存中;新的会话查询信息,就可以参照二级缓存中的内容;
3、sqlSession===EmployeeMapper==>Employee
DepartmentMapper===>Department
不同namespace查出的数据会放在自己对应的缓存中(map)
2.2 开启二级缓存
二级缓存默认不开启,需要手动配置
1、在全部配置文件中,开启全局二级缓存配置:<setting name="cacheEnabled" value="true"/>
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存。 | true | false | true |
虽然他的默认值是true,即开启二级缓存的,但是我们建议还是在全局配置文件中显式的配置,以免版本更迭,下个版本出来的时候,默认值变成了fasle。
2、到相关的mapper.xml文件中配置使用二级缓存,使用标签<cache></cache>
<cache>标签有下面几个属性:
• eviction=“ FIFO” :缓存回收策略,有如下四个值:
• LRU – 最近最少使用的:移除最长时间不被使用的对象。默认的是 LRU。
• FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
• SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
• WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
• flushInterval :刷新间隔,单位毫秒,• 默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
• size :引用数目,正整数 • 代表缓存最多可以存储多少个对象,太大容易导致内存溢出
• readOnly :只读,true/false
• true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。
• false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false。
• type:自定义缓存时,实现缓存的Java类全名。
3、我们对应的pojo需要实现序列化接口
2.3、演示二级缓存
2.3.1 在全局配置文件mybatis-config.properties中显式的开启二级缓存
<setting name="cacheEnabled" value="true"/>
2.3.2 在EmployeeMapper.xml中配置<cache>
<!--开启缓存,使用默认配置-->
<cache></cache>
2.3.3 Employee类实现Serializable接口
public class Employee implements Serializable {
}
2.3.4 测试
@Test
public void testSecondCache(){
//第一次会话
SqlSession sqlSession1 = sessionFactory.openSession();
EmployeeMapper employeeMapper = sqlSession1.getMapper(EmployeeMapper.class);
Employee employee = employeeMapper.selectById("1");//第一次查询,肯定走数据库
System.out.println(employee);
sqlSession1.close();//只有关闭会话,才会把数据存入到二级缓存中
//第二次会话
SqlSession sqlSession2 = sessionFactory.openSession();
EmployeeMapper employeeMapper2 = sqlSession2.getMapper(EmployeeMapper.class);
Employee employee2 = employeeMapper2.selectById("1");//第一次查询,肯定走数据库
System.out.println(employee2);
sqlSession2.close();
}
打印结果:
DEBUG 02-20 22:32:06,870 Cache Hit Ratio [com.mybatis.mapper.EmployeeMapper]: 0.0 (LoggingCache.java:62)
DEBUG 02-20 22:32:07,245 ==> Preparing: select id,last_name,age,addr from t_employee where id = ? (BaseJdbcLogger.java:145)
DEBUG 02-20 22:32:07,309 ==> Parameters: 1(String) (BaseJdbcLogger.java:145)
DEBUG 02-20 22:32:07,337 <== Total: 1 (BaseJdbcLogger.java:145)
Employee{id='1', lastName='tom', age=22, addr='changshashi'}
DEBUG 02-20 22:32:07,431 Cache Hit Ratio [com.mybatis.mapper.EmployeeMapper]: 0.5 (LoggingCache.java:62)
Employee{id='1', lastName='tom', age=22, addr='changshashi'}
可以从打印日志看出:每次都会Cache hit Ratio:计算缓存的命中率,第一次是0.0 ,也就是进行了一次查询,没有命中缓存。第二次的Cache Hit Ratio的值是0.5,也就是两次查询,第二次命中了缓存,命中率是0.5的意思。
二级缓存是namespace级别的缓存,也就是两次会话的结果都会缓存到EmployeeMapper的缓存中,第二次查询就能从缓存中获取数据了。
注意:查出的数据都会被默认先放在一级缓存中。只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中
这句话的意思是:如果我们把sqlSession1.close() 代码放到第二次方法后面,那么第二次查询也不会走缓存。因为我们的sqlSession1还没有关闭,数据只存在一级缓存,但是第二次查询又跟第一次不是同一个会话,所以是不会查询缓存的。
3、和缓存有关的设置/属性
1、cacheEnabled=true(默认值,开启二级缓存):false:关闭缓存(二级缓存关闭)(这个值不影响一级缓存,一级缓存一直可用的)。
2、每个select标签都有属性 useCache="true"(默认值true),false:不使用缓存(二级缓存不使用,一级缓存依然使用)。
3、每个增删改标签都有属性 flushCache="true"(默认值true):增删改执行完成后就会清除缓存(一级二级都会清除);每个查询标签也有属性 flushCache="false"(默认值false),查询操作不会清除缓存,如果查询标签设置:flushCache="true",每次查询之后都会清空缓存;缓存是没有被使用的;
4、sqlSession.clearCache(); 只是清楚当前session的一级缓存;二级缓存不受影响。
5、在全局配置文件的<settings>标签内可以设置 localCacheScope(本地缓存作用域,影响的是一级缓存),默认值是session(缓存数据保存到会话中),另一个值是STATEMENT(相当于禁用一级缓存)。
4、缓存原理图
5、MyBatis整合第三方缓存EhCache
EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。
为了整合第三方缓存,MyBatis定义了Cache接口方便我们进行自定义扩展。
5.1 整合步骤
1、导入第三方缓存包以及相关依赖包,还有整合相关的支持包,整合EhCache需要的包如下:
ehcache-core-2.6.8.jar、mybatis-ehcache-1.0.3.jar、slf4j-api-1.6.1.jar、slf4j-log4j12-1.6.2.jar
2、编写ehcache.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!-- 磁盘保存路径 -->
<diskStore path="D:\44\ehcache" />
<defaultCache
maxElementsInMemory="10000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
<!--
属性说明:
l diskStore:指定数据在磁盘中的存储位置。
l defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用<defalutCache/>指定的的管理策略
以下属性是必须的:
l maxElementsInMemory - 在内存中缓存的element的最大数目
l maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大
l eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断
l overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上
以下属性是可选的:
l timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大
l timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.
l diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
l diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作
l memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)
-->
3、在mapper.xml文件中配置cache标签,cache标签指定type属性,指向EhCache的实现
<mapper namespace="com.mybatis.mapper.EmployeeMapper">
<!--开启缓存,使用EhCache的缓存实现-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
</mapper>
参照缓存:若想在命名空间中共享相同的缓存配置和实例。可以使用 cache-ref 元素来引用另外一个缓存。
<mapper namespace="com.mybatis.mapper.DepartmentMapper">
<!--参照EmployeeMapper的缓存方案-->
<cache-ref namespace="com.mybatis.mapper.EmployeeMapper"/>
</mapper>
4、缓存原理:
注意:当执行一条查询SQL时,流程为:1 从二级缓存中进行查询;2 从一级缓存中查询;3 查询数据库。