Mybatis缓存

1 缓存分类

Mybatis的缓存机制:

如果没有缓存,每次查询的时候都需要从数据库中加载数据,会造成io的性能问题,所以,在很多情况下,连续执行两条相同的sql语句,可以直接从缓存中获取,如果获取不到,那么再去查数据库,这意味着查询完成的结果会放到缓存中。

Mybatis的缓存分类:

  1. 一级缓存:表示sqlSession级别的缓存,每次查询的时候会开启一个会话,此会话相当于一次连接,关闭之后自动失效
  2. 二级缓存:全局范围内的缓存,sqlSession关闭之后才会生效
  3. 第三方缓存:继承第三方的组件,来充当缓存的作用

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关闭之后才会生效,默认不开启,如果要开启需要做如下配置

  1. 修改全局配置文件,在setting中添加配置

    <setting name="cacheEnabled" value="true"/>
    
  2. 指定在哪个文件中使用缓存的配置(比如本次测试的EmpDao.xml)

    <cache/>
    
  3. 对应的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 面试问题

  1. 一级缓存和二级缓存有没有可能同时存在数据?

    不会同时存在,因为二级缓存生效的时候,是在sqlSession关闭的时候

  2. 当查询数据的时候,是先查询一级缓存还是二级缓存?

    先测试二级缓存

    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=[]}}
    

    结论:先查询二级缓存,在查询一级缓存

posted @ 2020-08-21 11:12  叶杨树  阅读(67)  评论(0)    收藏  举报