1、缓存机制介绍
2、一级缓存和二级缓存
查询的顺序是:
先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。 如果二级缓存没有命中,再查询一级缓存 如果一级缓存也没有命中,则查询数据库 SqlSession 关闭之前,一级缓存中的数据会写入二级缓存 从范围和作用域角度来说:
一级缓存:SqlSession 级别 二级缓存:SqlSessionFactory 级别
它们之间范围的大小参考下面图:
3、代码验证一级缓存 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 @Test public void testFirstLevelCache () { SqlSession session = factory.openSession(); EmployeeMapper mapper = session.getMapper(EmployeeMapper.class); Employee employee1 = mapper.selectEmployeeById(2 ); System.out.println("employee1 = " + employee1); Employee employee2 = mapper.selectEmployeeById(2 ); System.out.println("employee2 = " + employee2); System.out.println("(employee2 == employee1) = " + (employee2 == employee1)); System.out.println("employee1.equals(employee2) = " + employee1.equals(employee2)); System.out.println("employee1.hashCode() = " + employee1.hashCode()); System.out.println("employee2.hashCode() = " + employee2.hashCode()); session.commit(); session.close(); }
打印结果:
1 2 3 4 5 6 7 8 9 DEBUG 12 -01 09:14 :48 ,760 ==> Preparing: select emp_id,emp_name,emp_salary,emp_gender,emp_age from t_emp where emp_id=? (BaseJdbcLogger.java:145 ) DEBUG 12 -01 09:14 :48 ,804 ==> Parameters: 2 (Integer) (BaseJdbcLogger.java:145 ) DEBUG 12 -01 09:14 :48 ,830 <== Total: 1 (BaseJdbcLogger.java:145 ) employee1 = Employee{empId=2 , empName='AAAAAA' , empSalary=6666.66 , empAge=20 , empGender='male' } employee2 = Employee{empId=2 , empName='AAAAAA' , empSalary=6666.66 , empAge=20 , empGender='male' } (employee2 == employee1) = true employee1.equals(employee2) = true employee1.hashCode() = 1131645570 employee2.hashCode() = 1131645570
一共只打印了一条 SQL 语句,两个变量指向同一个对象。
4、一级缓存失效的情况 不是同一个 SqlSession 同一个 SqlSession 但是查询条件发生了变化 同一个 SqlSession 两次查询期间执行了任何一次增删改操作 同一个 SqlSession 两次查询期间手动清空了缓存 5、使用二级缓存 这里我们使用的是 Mybatis 自带的二级缓存。
① 开启二级缓存功能 在想要使用二级缓存的 Mapper 配置文件中加入 cache 标签
1 2 3 4 <mapper namespace ="com.atguigu.mybatis.EmployeeMapper" > <cache />
② 让实体类支持序列化 1 public class Employee implements Serializable {
③junit 测试 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 @Test public void testSecondLevelCacheExists () { SqlSession session = factory.openSession(); EmployeeMapper mapper = session.getMapper(EmployeeMapper.class); Employee employee = mapper.selectEmployeeById(2 ); System.out.println("employee = " + employee); session.close(); session = factory.openSession(); mapper = session.getMapper(EmployeeMapper.class); employee = mapper.selectEmployeeById(2 ); System.out.println("employee = " + employee); session.close(); }
打印效果:
1 2 3 4 5 6 7 DEBUG 12 -01 09:44 :27 ,057 Cache Hit Ratio [com.atguigu.mybatis.EmployeeMapper]: 0.0 (LoggingCache.java:62 ) DEBUG 12 -01 09:44 :27 ,459 ==> Preparing: select emp_id,emp_name,emp_salary,emp_gender,emp_age from t_emp where emp_id=? (BaseJdbcLogger.java:145 ) DEBUG 12 -01 09:44 :27 ,510 ==> Parameters: 2 (Integer) (BaseJdbcLogger.java:145 ) DEBUG 12 -01 09:44 :27 ,536 <== Total: 1 (BaseJdbcLogger.java:145 ) employee = Employee{empId=2 , empName='AAAAAA' , empSalary=6666.66 , empAge=20 , empGender='male' } DEBUG 12 -01 09:44 :27 ,622 Cache Hit Ratio [com.atguigu.mybatis.EmployeeMapper]: 0.5 (LoggingCache.java:62 ) employee = Employee{empId=2 , empName='AAAAAA' , empSalary=6666.66 , empAge=20 , empGender='male' }
④ 缓存命中率 日志中打印的 Cache Hit Ratio 叫做缓存命中率
1 2 3 4 5 Cache Hit Ratio [com.atguigu.mybatis.EmployeeMapper]: 0.0 (0 /1 ) Cache Hit Ratio [com.atguigu.mybatis.EmployeeMapper]: 0.5 (1 /2 ) Cache Hit Ratio [com.atguigu.mybatis.EmployeeMapper]: 0.6666666666666666 (2 /3 ) Cache Hit Ratio [com.atguigu.mybatis.EmployeeMapper]: 0.75 (3 /4 ) Cache Hit Ratio [com.atguigu.mybatis.EmployeeMapper]: 0.8 (4 /5 )
缓存命中率=命中缓存的次数/查询的总次数
⑤ 查询结果存入二级缓存的时机 结论:SqlSession 关闭的时候,一级缓存中的内容会被存入二级缓存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 SqlSession session01 = factory.openSession(); SqlSession session02 = factory.openSession(); EmployeeMapper employeeMapper01 = session01.getMapper(EmployeeMapper.class); EmployeeMapper employeeMapper02 = session02.getMapper(EmployeeMapper.class); Employee employee01 = employeeMapper01.selectEmployeeById(2 ); Employee employee02 = employeeMapper02.selectEmployeeById(2 ); System.out.println("employee02.equals(employee01) = " + employee02.equals(employee01));
上面代码打印的结果是:
1 2 3 4 5 6 7 8 9 DEBUG 12 -01 10 :10 :32 ,209 Cache Hit Ratio [com.atguigu.mybatis.EmployeeMapper]: 0.0 (LoggingCache.java:62 ) DEBUG 12 -01 10 :10 :32 ,570 ==> Preparing: select emp_id,emp_name,emp_salary,emp_gender,emp_age from t_emp where emp_id=? (BaseJdbcLogger.java:145 ) DEBUG 12 -01 10 :10 :32 ,624 ==> Parameters: 2 (Integer) (BaseJdbcLogger.java:145 ) DEBUG 12 -01 10 :10 :32 ,643 <== Total: 1 (BaseJdbcLogger.java:145 ) DEBUG 12 -01 10 :10 :32 ,644 Cache Hit Ratio [com.atguigu.mybatis.EmployeeMapper]: 0.0 (LoggingCache.java:62 ) DEBUG 12 -01 10 :10 :32 ,661 ==> Preparing: select emp_id,emp_name,emp_salary,emp_gender,emp_age from t_emp where emp_id=? (BaseJdbcLogger.java:145 ) DEBUG 12 -01 10 :10 :32 ,662 ==> Parameters: 2 (Integer) (BaseJdbcLogger.java:145 ) DEBUG 12 -01 10 :10 :32 ,665 <== Total: 1 (BaseJdbcLogger.java:145 ) employee02.equals(employee01) = false
修改代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 SqlSession session01 = factory.openSession(); SqlSession session02 = factory.openSession(); EmployeeMapper employeeMapper01 = session01.getMapper(EmployeeMapper.class); EmployeeMapper employeeMapper02 = session02.getMapper(EmployeeMapper.class); Employee employee01 = employeeMapper01.selectEmployeeById(2 ); session01.close(); Employee employee02 = employeeMapper02.selectEmployeeById(2 ); System.out.println("employee02.equals(employee01) = " + employee02.equals(employee01)); session02.close();
打印结果:
1 2 3 4 5 6 DEBUG 12 -01 10 :14 :06 ,804 Cache Hit Ratio [com.atguigu.mybatis.EmployeeMapper]: 0.0 (LoggingCache.java:62 ) DEBUG 12 -01 10 :14 :07 ,135 ==> Preparing: select emp_id,emp_name,emp_salary,emp_gender,emp_age from t_emp where emp_id=? (BaseJdbcLogger.java:145 ) DEBUG 12 -01 10 :14 :07 ,202 ==> Parameters: 2 (Integer) (BaseJdbcLogger.java:145 ) DEBUG 12 -01 10 :14 :07 ,224 <== Total: 1 (BaseJdbcLogger.java:145 ) DEBUG 12 -01 10 :14 :07 ,308 Cache Hit Ratio [com.atguigu.mybatis.EmployeeMapper]: 0.5 (LoggingCache.java:62 ) employee02.equals(employee01) = false
⑥ 二级缓存相关配置 在 Mapper 配置文件中添加的 cache 标签可以设置一些属性:
eviction 属性:缓存回收策略
LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象。
FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
默认的是 LRU。
flushInterval 属性:刷新间隔,单位毫秒
默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
size 属性:引用数目,正整数
代表缓存最多可以存储多少个对象,太大容易导致内存溢出
readOnly 属性:只读,true/false
true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。
false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false。
6、Mybatis 整合 EHCache EHCache 作为第三方专门的缓存产品,相比 Mybatis 自带的缓存机制更加专业一些。
① 搭建 EHCache 使用环境 [1]在 Mybatis 环境基础上加入 jar 包 ehcache-core-2.6.8.jar:EHCache 核心包 mybatis-ehcache-1.0.3.jar:Mybatis 和 EHCache 的整合包 slf4j-api-1.6.1.jar:SFL4J 是一个日志标准 slf4j-log4j12-1.6.2.jar:SFL4J 标准下 log4j 的实现 [2]加入配置文件 文件名:ehcache.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?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:\atguigu\ehcache" /> <defaultCache maxElementsInMemory="1000" maxElementsOnDisk="10000000" eternal="false" overflowToDisk="true" timeToIdleSeconds="120" timeToLiveSeconds="120" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> </defaultCache > </ehcache >
引入第三方框架或工具时,配置文件的文件名可以自定义吗?
可以自定义:文件名是由我告诉其他环境 不能自定义:文件名是框架内置的、约定好的,就不能自定义,以避免框架无法加载这个文件 [3]指定缓存管理器的具体类型 在 Mapper 配置文件的 cache 标签内设置 type 属性
1 <cache type ="org.mybatis.caches.ehcache.EhcacheCache" />
②junit 测试 正常按照二级缓存的方式测试即可。因为整合 EHCache 后,其实就是使用 EHCache 代替了 Mybatis 自带的二级缓存。
③EHCache 配置文件说明 diskStore 标签:指定数据在磁盘中的存储位置。 defaultCache 标签:当借助 CacheManager.add(“demoCache”)创建 Cache 时,EhCache 便会采用指定的的管理策略
以下属性是必须的: 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(先进先出)
7、缓存的基本原理 集中体现在 org.apache.ibatis.cache.impl.PerpetualCache 类中,内部以 Map 的形式维护缓存数据的。