Mybatis缓存
1 缓存分类
Mybatis的缓存机制:
如果没有缓存,每次查询的时候都需要从数据库中加载数据,会造成io的性能问题,所以,在很多情况下,连续执行两条相同的sql语句,可以直接从缓存中获取,如果获取不到,那么再去查数据库,这意味着查询完成的结果会放到缓存中。
Mybatis的缓存分类:
- 一级缓存:表示sqlSession级别的缓存,每次查询的时候会开启一个会话,此会话相当于一次连接,关闭之后自动失效
- 二级缓存:全局范围内的缓存,sqlSession关闭之后才会生效
- 第三方缓存:继承第三方的组件,来充当缓存的作用
Mybatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。
默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:
<cache/>
2 一级缓存
表示数据存储在sqlSession中,关闭之后自动失效,默认情况下是开启的
在同一个会话之内,如果执行了多个相同的sql语句,那么除了第一个之外,所有的数据都是从缓存中进行查询的
sql
<select id="selectEmp" resultType="com.yeyangshu.mybatis.bean.Emp">
select * from emp where empno = #{empno}
</select>
test
Emp emp2 = empDao.selectEmp(7369);
System.out.println(emp2);
Emp emp3 = empDao.selectEmp(7369);
System.out.println(emp3);
sqlSession.close();
result
09:50:14.113 [main] DEBUG com.yeyangshu.mybatis.dao.EmpDao.selectEmp - ==> Preparing: select * from emp where empno = ?
09:50:14.143 [main] DEBUG com.yeyangshu.mybatis.dao.EmpDao.selectEmp - ==> Parameters: 7369(Integer)
09:50:14.170 [main] DEBUG com.yeyangshu.mybatis.dao.EmpDao.selectEmp - <== Total: 1
Emp{empno=7369, ename='SMITH', job='CLERK', mgr=7902, hiredate=Wed Dec 17 08:00:00 CST 1980, sal=800.0, common=null, dept=null}
Emp{empno=7369, ename='SMITH', job='CLERK', mgr=7902, hiredate=Wed Dec 17 08:00:00 CST 1980, sal=800.0, common=null, dept=null}
在一些情况,一级缓存可能会失效
-
在同一个方法中可能会开启多个会话,注意,会话和方法没有关系,不是一个方法只能有一个会话,所以一定记住,缓存的数据是保存在Session里面的
SqlSession sqlSession = sqlSessionFactory.openSession(true); Emp emp = empDao.selectEmp(7369); System.out.println(emp); SqlSession sqlSession2 = sqlSessionFactory.openSession(true); Emp emp2 = empDao.selectEmp(7369); System.out.println(emp2); 此时session2不会使用session的结果
-
当传递对象的时候,如果对象中的属性不同,也不会走缓存
empDao.selectEmpByCondition(emp)
-
在同一连接中,如果修改了数据,那么缓存会失效,不同连接之间是不受影响的
-
如果在一个会话中,手动清空了缓存,那么缓存也会失效
sqlSession.clearCache();
3 二级缓存
3.1 二级缓存配置
二级缓存表示的是全局缓存,必须要等到sqlSession关闭之后才会生效,默认不开启,如果要开启需要做如下配置
-
修改全局配置文件,在setting中添加配置
<setting name="cacheEnabled" value="true"/>
-
指定在哪个文件中使用缓存的配置(比如本次测试的EmpDao.xml)
<cache/>
-
对应的Java实体类必须要实现序列化的接口
test
EmpDao empDao = sqlSession.getMapper(EmpDao.class);
EmpDao empDao2 = sqlSession2.getMapper(EmpDao.class);
//二级缓存
Emp emp = empDao.selectEmpByEmpno(7369);
System.out.println(emp);
sqlSession.close();
System.out.println("================================================");
Emp emp2 = empDao2.selectEmpByEmpno(7369);
System.out.println(emp2);
sqlSession2.close();
result
//Cache Hit Ratio 指的是缓存命中率
09:28:46.121 [main] DEBUG com.yeyangshu.mybatis.dao.EmpDao - Cache Hit Ratio [com.yeyangshu.mybatis.dao.EmpDao]: 0.0
09:28:46.128 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
09:28:46.298 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 1101048445.
09:28:46.300 [main] DEBUG com.yeyangshu.mybatis.dao.EmpDao.selectEmpByEmpno - ==> Preparing: select * from emp where empno = ?
09:28:46.327 [main] DEBUG com.yeyangshu.mybatis.dao.EmpDao.selectEmpByEmpno - ==> Parameters: 7369(Integer)
09:28:46.352 [main] DEBUG com.yeyangshu.mybatis.dao.DeptDao.getDeptAndEmps - ====> Preparing: select * from dept where deptno = ?
09:28:46.352 [main] DEBUG com.yeyangshu.mybatis.dao.DeptDao.getDeptAndEmps - ====> Parameters: 20(Integer)
09:28:46.354 [main] DEBUG com.yeyangshu.mybatis.dao.DeptDao.getDeptAndEmps - <==== Total: 1
09:28:46.354 [main] DEBUG com.yeyangshu.mybatis.dao.EmpDao.selectEmpByEmpno - <== Total: 1
Emp{empno=7369, ename='SMITH', job='CLERK', mgr=7902, hiredate=Wed Dec 17 08:00:00 CST 1980, sal=800.0, common=null, dept=Dept{deptno=20, dname='RESEARCH', loc='DALLAS', emps=[]}}
09:28:46.361 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@41a0aa7d]
09:28:46.361 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 1101048445 to pool.
================================================
09:28:46.363 [main] DEBUG com.yeyangshu.mybatis.dao.EmpDao - Cache Hit Ratio [com.yeyangshu.mybatis.dao.EmpDao]: 0.5
Emp{empno=7369, ename='SMITH', job='CLERK', mgr=7902, hiredate=Wed Dec 17 08:00:00 CST 1980, sal=800.0, common=null, dept=Dept{deptno=20, dname='RESEARCH', loc='DALLAS', emps=[]}}
3.2 二级缓存属性
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
3.2.1 eviction
缓存淘汰机制:
- LRU:最近最少使用:移除最长时间不被使用的对象。
- FIFO:先进先出:按对象进入缓存的顺序来移除它们。
- SOFT:软引用:基于垃圾回收器状态和软引用规则移除对象。
- WEAK:弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
3.2.2 flushInterval
设置多长时间进行缓存的刷新
官网:可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
3.2.3 size
正整数,缓存中可以设置存储多少个对象,一般不设置,如果设置的话不要太大,会导致内存溢出
3.2.4 readOnly
-
true:只读属性,会给所有的调用的方法返回该对象的实例,不安全
-
false:读写缓存,只会返回缓存对象的拷贝,比较安全
3.3 第三方缓存Ehcache
在某些情况下我们也可以自定义实现缓存,或为其他第三方缓存方案创建适配器,来完全覆盖缓存行为。
Mybatis的仓库:https://github.com/mybatis
Ehcache仓库 :https://github.com/mybatis/ehcache-cache
官方文档:http://mybatis.org/ehcache-cache/
3.3.1 pom配置
导入依赖
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
3.3.2 配置文件详解
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<!-- 磁盘保存路径 -->
<diskStore path="D:\ehcache" />
<defaultCache
maxElementsInMemory="1"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
<!--
属性说明:
diskStore:指定数据在磁盘中的存储位置。
defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用<defalutCache/>指定的的管理策略
以下属性是必须的:
maxElementsInMemory - 在内存中缓存的element的最大数目
maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大
eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断
overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上
以下属性是可选的:
timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大
timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.
diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作
memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)
-->
3.3.3 mapper配置
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
3.3.4 测试
测试之后会产生两个文件
4 面试问题
-
一级缓存和二级缓存有没有可能同时存在数据?
不会同时存在,因为二级缓存生效的时候,是在sqlSession关闭的时候
-
当查询数据的时候,是先查询一级缓存还是二级缓存?
先测试二级缓存
test
EmpDao empDao = sqlSession.getMapper(EmpDao.class); EmpDao empDao2 = sqlSession2.getMapper(EmpDao.class); Emp emp = empDao.selectEmpByEmpno(7369); System.out.println(emp); sqlSession.close(); System.out.println("================================================"); Emp emp2 = empDao2.selectEmpByEmpno(7369); System.out.println(emp2); Emp emp3 = empDao2.selectEmpByEmpno(7369); System.out.println(emp3); sqlSession2.close();
result
10:14:31.810 [main] DEBUG com.yeyangshu.mybatis.dao.EmpDao - Cache Hit Ratio [com.yeyangshu.mybatis.dao.EmpDao]: 0.0 10:14:31.815 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection 10:14:32.008 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 1101048445. 10:14:32.011 [main] DEBUG com.yeyangshu.mybatis.dao.EmpDao.selectEmpByEmpno - ==> Preparing: select * from emp where empno = ? 10:14:32.040 [main] DEBUG com.yeyangshu.mybatis.dao.EmpDao.selectEmpByEmpno - ==> Parameters: 7369(Integer) 10:14:32.068 [main] DEBUG com.yeyangshu.mybatis.dao.DeptDao.getDeptAndEmps - ====> Preparing: select * from dept where deptno = ? 10:14:32.068 [main] DEBUG com.yeyangshu.mybatis.dao.DeptDao.getDeptAndEmps - ====> Parameters: 20(Integer) 10:14:32.070 [main] DEBUG com.yeyangshu.mybatis.dao.DeptDao.getDeptAndEmps - <==== Total: 1 10:14:32.070 [main] DEBUG com.yeyangshu.mybatis.dao.EmpDao.selectEmpByEmpno - <== Total: 1 Emp{empno=7369, ename='SMITH', job='CLERK', mgr=7902, hiredate=Wed Dec 17 08:00:00 CST 1980, sal=800.0, common=null, dept=Dept{deptno=20, dname='RESEARCH', loc='DALLAS', emps=[]}} 10:14:32.079 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@41a0aa7d] 10:14:32.079 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 1101048445 to pool. ================================================ 10:14:32.082 [main] DEBUG com.yeyangshu.mybatis.dao.EmpDao - Cache Hit Ratio [com.yeyangshu.mybatis.dao.EmpDao]: 0.5 Emp{empno=7369, ename='SMITH', job='CLERK', mgr=7902, hiredate=Wed Dec 17 08:00:00 CST 1980, sal=800.0, common=null, dept=Dept{deptno=20, dname='RESEARCH', loc='DALLAS', emps=[]}} 10:14:32.083 [main] DEBUG com.yeyangshu.mybatis.dao.EmpDao - Cache Hit Ratio [com.yeyangshu.mybatis.dao.EmpDao]: 0.6666666666666666 Emp{empno=7369, ename='SMITH', job='CLERK', mgr=7902, hiredate=Wed Dec 17 08:00:00 CST 1980, sal=800.0, common=null, dept=Dept{deptno=20, dname='RESEARCH', loc='DALLAS', emps=[]}}
再测试一级缓存
test
//二级缓存 Emp emp = empDao.selectEmpByEmpno(7369); System.out.println(emp); sqlSession.close(); System.out.println("================================================"); Emp emp2 = empDao2.selectEmpByEmpno(7369); System.out.println(emp2); Emp emp3 = empDao2.selectEmpByEmpno(7369); System.out.println(emp3); Emp emp4 = empDao2.selectEmpByEmpno(7499); System.out.println(emp4); Emp emp5 = empDao2.selectEmpByEmpno(7499); System.out.println(emp5; sqlSession2.close();
result
//emp1先去二级缓存中找,没有找到,命中率0/1=0 03.445 [main] DEBUG com.yeyangshu.mybatis.dao.EmpDao - Cache Hit Ratio [com.yeyangshu.mybatis.dao.EmpDao]: 0.0 10:27:03.452 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection 10:27:03.640 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 1158258131. 10:27:03.642 [main] DEBUG com.yeyangshu.mybatis.dao.EmpDao.selectEmpByEmpno - ==> Preparing: select * from emp where empno = ? 10:27:03.679 [main] DEBUG com.yeyangshu.mybatis.dao.EmpDao.selectEmpByEmpno - ==> Parameters: 7369(Integer) 10:27:03.713 [main] DEBUG com.yeyangshu.mybatis.dao.DeptDao.getDeptAndEmps - ====> Preparing: select * from dept where deptno = ? 10:27:03.713 [main] DEBUG com.yeyangshu.mybatis.dao.DeptDao.getDeptAndEmps - ====> Parameters: 20(Integer) 10:27:03.714 [main] DEBUG com.yeyangshu.mybatis.dao.DeptDao.getDeptAndEmps - <==== Total: 1 10:27:03.714 [main] DEBUG com.yeyangshu.mybatis.dao.EmpDao.selectEmpByEmpno - <== Total: 1 Emp{empno=7369, ename='SMITH', job='CLERK', mgr=7902, hiredate=Wed Dec 17 08:00:00 CST 1980, sal=800.0, common=null, dept=Dept{deptno=20, dname='RESEARCH', loc='DALLAS', emps=[]}} ================================================ //emp2先去二级缓存中找,找到了,命中率1/2=0.5 10:27:03.724 [main] DEBUG com.yeyangshu.mybatis.dao.EmpDao - Cache Hit Ratio [com.yeyangshu.mybatis.dao.EmpDao]: 0.5 Emp{empno=7369, ename='SMITH', job='CLERK', mgr=7902, hiredate=Wed Dec 17 08:00:00 CST 1980, sal=800.0, common=null, dept=Dept{deptno=20, dname='RESEARCH', loc='DALLAS', emps=[]}} //emp3先去二级缓存中找,找到了,命中率2/3=0.6666666666666666 10:27:03.725 [main] DEBUG com.yeyangshu.mybatis.dao.EmpDao - Cache Hit Ratio [com.yeyangshu.mybatis.dao.EmpDao]: 0.6666666666666666 Emp{empno=7369, ename='SMITH', job='CLERK', mgr=7902, hiredate=Wed Dec 17 08:00:00 CST 1980, sal=800.0, common=null, dept=Dept{deptno=20, dname='RESEARCH', loc='DALLAS', emps=[]}} //emp4会先去二级缓存里面找,没有找到,命中率2/4=0.5 10:27:03.725 [main] DEBUG com.yeyangshu.mybatis.dao.EmpDao - Cache Hit Ratio [com.yeyangshu.mybatis.dao.EmpDao]: 0.5 10:27:03.725 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection 10:27:03.725 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Checked out connection 1158258131 from pool. 10:27:03.725 [main] DEBUG com.yeyangshu.mybatis.dao.EmpDao.selectEmpByEmpno - ==> Preparing: select * from emp where empno = ? 10:27:03.725 [main] DEBUG com.yeyangshu.mybatis.dao.EmpDao.selectEmpByEmpno - ==> Parameters: 7499(Integer) 10:27:03.726 [main] DEBUG com.yeyangshu.mybatis.dao.DeptDao.getDeptAndEmps - ====> Preparing: select * from dept where deptno = ? 10:27:03.726 [main] DEBUG com.yeyangshu.mybatis.dao.DeptDao.getDeptAndEmps - ====> Parameters: 30(Integer) 10:27:03.727 [main] DEBUG com.yeyangshu.mybatis.dao.DeptDao.getDeptAndEmps - <==== Total: 1 10:27:03.727 [main] DEBUG com.yeyangshu.mybatis.dao.EmpDao.selectEmpByEmpno - <== Total: 1 Emp{empno=7499, ename='ALLEN', job='SALESMAN', mgr=7698, hiredate=Fri Feb 20 08:00:00 CST 1981, sal=1600.0, common=300.0, dept=Dept{deptno=30, dname='SALES', loc='CHICAGO', emps=[]}} //emp5会先去二级缓存里面找,没有找到,命中率2/5=0.4,在一级缓存中找到了 10:27:03.727 [main] DEBUG com.yeyangshu.mybatis.dao.EmpDao - Cache Hit Ratio [com.yeyangshu.mybatis.dao.EmpDao]: 0.4 Emp{empno=7499, ename='ALLEN', job='SALESMAN', mgr=7698, hiredate=Fri Feb 20 08:00:00 CST 1981, sal=1600.0, common=300.0, dept=Dept{deptno=30, dname='SALES', loc='CHICAGO', emps=[]}}
结论:先查询二级缓存,在查询一级缓存