本篇文章记录MyBatis的缓存原理学习笔记。掌握缓存机制对于学习和使用MyBatis框架是十分有好处的。这是以前学习mybatis时记录的笔记,晚上翻了出来,就贴在这里。
缓存机制
MyBatis默认定义了两级缓存。分为一级缓存和二级缓存。一级缓存是SQLSession级别的缓存,也成为本地缓存。二级缓存是基于namespace级别的缓存。
- 默认情况下,只有一级缓存开启
- 二级缓存需要手动开启和配置
- 我们可以通过实现Cache接口来自定义二级缓存
一级缓存
一级缓存,也叫本地缓存,与数据库同一次会话期间查询到的数据会放在本地缓存中。以后如果需要获取相同的数据,直接从缓存中取,没有必要再去查数据库。
看一下单元测试:
1/**
2 *
3 * 测试一级缓存
4 *
5 * @param
6 */
7 @org.junit.Test
8 public void testFirstLevelCache() {
9 SqlSession session = null;
10 try {
11 session = sqlSessionFactory.openSession(true);// 获取session
12 EmployeeDAO dao = session.getMapper(EmployeeDAO.class);// 得到DAO
13
14 Employee e1 = dao.getEmployeeByID(103812351);
15 System.out.println("查询到的员工e1:" + e1.hashCode() + "==" + e1);
16 Employee e2 = dao.getEmployeeByID(103812351);
17 System.out.println("查询到的员工e2:" + e2.hashCode() + "==" + e2);
18 } catch (Exception e) {
19 e.printStackTrace();
20 } finally {
21 if (session != null) {
22 session.close();
23 }
24 }
25 }
执行单元测试,控制台的输出:
1DEBUG 05-14 23:56:41,246 ==> Preparing: SELECT * FROM employees where emp_no=?; (BaseJdbcLogger.java:159)
2DEBUG 05-14 23:56:41,290 ==> Parameters: 103812351(Integer) (BaseJdbcLogger.java:159)
3DEBUG 05-14 23:56:41,338 <== Total: 1 (BaseJdbcLogger.java:159)
4查询到的员工e1:1391624125==Employee [empNo=103812351, birthDate=1991-12-12, firstName=first2, lastName=last2, gender=M, hireDate=2017-05-14]
5查询到的员工e2:1391624125==Employee [empNo=103812351, birthDate=1991-12-12, firstName=first2, lastName=last2, gender=M, hireDate=2017-05-14]
从输出结果可以看出,两次调用getEmployeeByID方法,只向数据库发送一条sql查询。而且查询到的两个对象e1和e2的hashcode相同,说明第二次调用方法时,是从一级缓存中去取的对象。
一级缓存在以下4种情况下会失效:
- SQLSession不同
- SQLSession相同,但查询条件不同(当前缓存中还没有这个数据)
- SQLSession相同,但两次查询期间执行了增删改(这次增删改可能对当前数据有影响)
- SQLSession相同,手动清除了一级缓存
测试第一种情况:
1/**
2*
3* 测试一级缓存
4*
5* @param
6*/
7@org.junit.Test
8public void testFirstLevelCache() {
9SqlSession session = null;
10try {
11session = sqlSessionFactory.openSession(true);// 获取session
12EmployeeDAO dao = session.getMapper(EmployeeDAO.class);// 得到DAO
13 Employee e1 = dao.getEmployeeByID(103812351);
14 System.out.println("查询到的员工e1:" + e1.hashCode() + "==" + e1);
15 SqlSession session2 = sqlSessionFactory.openSession(true);// 获取新的session
16 EmployeeDAO dao2 = session2.getMapper(EmployeeDAO.class);// 得到DAO
17 Employee e2 = dao2.getEmployeeByID(103812351);
18 System.out.println("查询到的员工e2:" + e2.hashCode() + "==" + e2);
19 session2.close();
20 } catch (Exception e) {
21 e.printStackTrace();
22 } finally {
23 if (session != null) {
24 session.close();
25 }
26 }
27}
控制台输出:
1 DEBUG 05-15 00:11:12,066 ==> Preparing: SELECT * FROM employees where emp_no=?; (BaseJdbcLogger.java:159)
2 DEBUG 05-15 00:11:12,098 ==> Parameters: 103812351(Integer) (BaseJdbcLogger.java:159)
3 DEBUG 05-15 00:11:12,130 <== Total: 1 (BaseJdbcLogger.java:159)
4 查询到的员工e1:1171802656==Employee [empNo=103812351, birthDate=1991-12-12, firstName=first2, lastName=last2, gender=M, hireDate=2017-05-14]
5 DEBUG 05-15 00:11:12,243 ==> Preparing: SELECT * FROM employees where emp_no=?; (BaseJdbcLogger.java:159)
6 DEBUG 05-15 00:11:12,244 ==> Parameters: 103812351(Integer) (BaseJdbcLogger.java:159)
7 DEBUG 05-15 00:11:12,254 <== Total: 1 (BaseJdbcLogger.java:159)
8 查询到的员工e2:1892627171==Employee [empNo=103812351, birthDate=1991-12-12, firstName=first2, lastName=last2, gender=M, hireDate=2017-05-14]
可以看到,第二次查询使用的是新的session,结果执行了两次查询请求,验证了第一种情况。
来看第二种情况的单元测试:
1/**
2 *
3 * 测试一级缓存
4 *
5 * @param
6 */
7 @org.junit.Test
8 public void testFirstLevelCache() {
9 SqlSession session = null;
10 try {
11 session = sqlSessionFactory.openSession(true);// 获取session
12 EmployeeDAO dao = session.getMapper(EmployeeDAO.class);// 得到DAO
13
14 Employee e1 = dao.getEmployeeByID(103812351);
15 System.out.println("查询到的员工e1:" + e1.hashCode() + "==" + e1);
16 Employee e2 = dao.getEmployeeByID(103812352);
17 System.out.println("查询到的员工e2:" + e2.hashCode() + "==" + e2);
18 } catch (Exception e) {
19 e.printStackTrace();
20 } finally {
21 if (session != null) {
22 session.close();
23 }
24 }
25 }
输出为:
1DEBUG 05-15 00:14:25,221 ==> Preparing: SELECT * FROM employees where emp_no=?; (BaseJdbcLogger.java:159)
2DEBUG 05-15 00:14:25,261 ==> Parameters: 103812351(Integer) (BaseJdbcLogger.java:159)
3DEBUG 05-15 00:14:25,300 <== Total: 1 (BaseJdbcLogger.java:159)
4查询到的员工e1:1391624125==Employee [empNo=103812351, birthDate=1991-12-12, firstName=first2, lastName=last2, gender=M, hireDate=2017-05-14]
5DEBUG 05-15 00:14:25,315 ==> Preparing: SELECT * FROM employees where emp_no=?; (BaseJdbcLogger.java:159)
6DEBUG 05-15 00:14:25,316 ==> Parameters: 103812352(Integer) (BaseJdbcLogger.java:159)
7DEBUG 05-15 00:14:25,326 <== Total: 1 (BaseJdbcLogger.java:159)
8查询到的员工e2:1292738535==Employee [empNo=103812352, birthDate=1991-12-12, firstName=first3, lastName=last3, gender=M, hireDate=2017-05-14]
这种情况下,第一要的是工号为103812351的员工,第二次查询103812352号员工,两次查询的数据是不同的,很明显需要查询两次数据库。
来看第三种情况,
1/**
2 *
3 * 测试一级缓存
4 *
5 * @param
6 */
7 @org.junit.Test
8 public void testFirstLevelCache() {
9 SqlSession session = null;
10 try {
11 session = sqlSessionFactory.openSession(true);// 获取session
12 EmployeeDAO dao = session.getMapper(EmployeeDAO.class);// 得到DAO
13
14 Employee e1 = dao.getEmployeeByID(103812351);
15 System.out.println("查询到的员工e1:" + e1.hashCode() + "==" + e1);
16
17 DynamicSqlDAO dao2 = session.getMapper(DynamicSqlDAO.class);// 得到DynamicSqlDAO
18 int count = dao2.updateFirstNameAndLastNameByEmpNo("dong3", null, 103842131);// 更新操作
19
20 Employee e2 = dao.getEmployeeByID(103812351);
21 System.out.println("查询到的员工e2:" + e2.hashCode() + "==" + e2);
22
23 } catch (Exception e) {
24 e.printStackTrace();
25 } finally {
26 if (session != null) {
27 session.close();
28 }
29 }
30 }
这种情况的输出结果为:
1DEBUG 05-15 00:23:28,023 ==> Preparing: SELECT * FROM employees where emp_no=?; (BaseJdbcLogger.java:159)
2DEBUG 05-15 00:23:28,054 ==> Parameters: 103812351(Integer) (BaseJdbcLogger.java:159)
3DEBUG 05-15 00:23:28,097 <== Total: 1 (BaseJdbcLogger.java:159)
4查询到的员工e1:1391624125==Employee [empNo=103812351, birthDate=1991-12-12, firstName=first2, lastName=last2, gender=M, hireDate=2017-05-14]
5DEBUG 05-15 00:23:28,174 ==> Preparing: update employees set first_name = ? where emp_no=? (BaseJdbcLogger.java:159)
6DEBUG 05-15 00:23:28,175 ==> Parameters: dong3(String), 103842131(Integer) (BaseJdbcLogger.java:159)
7DEBUG 05-15 00:23:28,211 <== Updates: 1 (BaseJdbcLogger.java:159)
8DEBUG 05-15 00:23:28,211 ==> Preparing: SELECT * FROM employees where emp_no=?; (BaseJdbcLogger.java:159)
9DEBUG 05-15 00:23:28,211 ==> Parameters: 103812351(Integer) (BaseJdbcLogger.java:159)
10DEBUG 05-15 00:23:28,221 <== Total: 1 (BaseJdbcLogger.java:159)
11查询到的员工e2:554348863==Employee [empNo=103812351, birthDate=1991-12-12, firstName=first2, lastName=last2, gender=M, hireDate=2017-05-14]
首先查询员工e1,然后调用了另一个mapper中的update方法,更新的employees表中的数据,然后再次查询同一工号的员工。输出结果显示,第二次查询执行了新的sql查询请求,两次查询到的对象也不是同一个。
接着看最后一种情况:
1session = sqlSessionFactory.openSession(true);// 获取session
2EmployeeDAO dao = session.getMapper(EmployeeDAO.class);// 得到DAO
3
4Employee e1 = dao.getEmployeeByID(103812351);
5System.out.println("查询到的员工e1:" + e1.hashCode() + "==" + e1);
6
7session.clearCache();
8
9Employee e2 = dao.getEmployeeByID(103812351);
10System.out.println("查询到的员工e2:" + e2.hashCode() + "==" + e2);
控制台的输出是:
1DEBUG 05-15 00:26:10,957 ==> Preparing: SELECT * FROM employees where emp_no=?; (BaseJdbcLogger.java:159)
2DEBUG 05-15 00:26:10,989 ==> Parameters: 103812351(Integer) (BaseJdbcLogger.java:159)
3DEBUG 05-15 00:26:11,022 <== Total: 1 (BaseJdbcLogger.java:159)
4查询到的员工e1:1391624125==Employee [empNo=103812351, birthDate=1991-12-12, firstName=first2, lastName=last2, gender=M, hireDate=2017-05-14]
5DEBUG 05-15 00:26:11,024 ==> Preparing: SELECT * FROM employees where emp_no=?; (BaseJdbcLogger.java:159)
6DEBUG 05-15 00:26:11,024 ==> Parameters: 103812351(Integer) (BaseJdbcLogger.java:159)
7DEBUG 05-15 00:26:11,037 <== Total: 1 (BaseJdbcLogger.java:159)
8查询到的员工e2:1292738535==Employee [empNo=103812351, birthDate=1991-12-12, firstName=first2, lastName=last2, gender=M, hireDate=2017-05-14]
session.clearCache()清除了缓存,所以需要再次发送请求。
二级缓存
二级缓存,也叫全局缓存,它是基于namespace级别的缓存,一个namespace对应于一个缓存。工作机制:
- 一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中
- 如果会话关闭,一级会话中的数据会被放在二级缓存中。新的会话查询信息,就可以参考二级缓存。
使用步骤:
- 开启全局二级缓存配置。即在全局配置文件中配置:
1 <!-- 开启全局二级缓存配置 -->
2<setting name="cacheEnabled" value="true"></setting>
2.在sql映射文件mapper.xml中配置使用二级缓存:
1<mapper namespace="cn.codefish.mybatis.dao.EmployeeDAO">
2 <!-- <cache eviction="LRU" flushInterval="6000" readOnly="false" size="1024" type=""></cache> -->
3 <!--
4 eviction:缓存的回收策略:
5 LRU:最近最少使用的,移除最长时间不被使用的对象
6 FIFO:先进先出,按对象进入缓存的顺序来移除它们
7 SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象
8 WEAK:弱引用,更积极地移除基于垃圾回收器状态和弱引用规则的对象
9 flushInterval:缓存回收间隔,缓存多长时间清空一次,默认不清空。设置一个毫秒值。
10 readOnly:缓存是否只读。
11 true:只读。MyBatis认为从缓存中取出的操作都是只读操作,不会被修改掉。MyBatis为了加快数据的获取速度,直接把数据在缓存中的引用交给用户。不安全,速度快。
12 false:非只读。MyBatis认为获取的数据会被修改,于是会利用序列化和反序列化克隆一份数据给你。
13 size:缓存中存放多少个元素
14 type:自定义缓存,需要实现cache接口。
15 -->
16
17 <cache eviction="LRU" flushInterval="6000" readOnly="false" size="1024" ></cache>
18 <select id="getEmployeeByID" resultType="cn.codefish.mybatis.dao.Employee" databaseId="mysql">
19 SELECT * FROM employees where emp_no=#{id};
20 </select>
21</mapper>
xml文件中Cache元素几个属性的含义:
- eviction:缓存的回收策略: LRU:最近最少使用的,移除最长时间不被使用的对象 FIFO:先进先出,按对象进入缓存的顺序来移除它们 SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象 WEAK:弱引用,更积极地移除基于垃圾回收器状态和弱引用规则的对象
- flushInterval:缓存回收间隔,缓存多长时间清空一次,默认不清空。设置一个毫秒值。
- readOnly:缓存是否只读。
- true:只读。MyBatis认为从缓存中取出的操作都是只读操作,不会被修改掉。MyBatis为了加快数据的获取速度,直接把数据在缓存中的引用交给用户。不安全,速度快。
- false:非只读。MyBatis认为获取的数据会被修改,于是会利用序列化和反序列化克隆一份数据给你。
- size:缓存中存放多少个元素
- type:自定义缓存,需要实现cache接口。
3.我们的POJO要实现序列化接口。因为缓存期间会用到序列化和反序列化技术。
下面进行测试,单元测试代码:
1/**
2 * 测试二级缓存
3 *
4 * @param list
5 */
6 @org.junit.Test
7 public void testSecondLevelCache() {
8 SqlSession session = null;
9 try {
10 session = sqlSessionFactory.openSession(true);// 获取session
11 EmployeeDAO dao = session.getMapper(EmployeeDAO.class);// 得到DAO
12 // 组装数据
13 Employee e = dao.getEmployeeByID(10022);
14 System.out.println("e1 hashcode:" + e.hashCode());
15 session.close();// 关闭session
16 session = sqlSessionFactory.openSession(true);// 再次获取session
17 dao = session.getMapper(EmployeeDAO.class);// 再次得到DAO
18 Employee e2 = dao.getEmployeeByID(10022);
19 System.out.println("e2 hashcode:" + e2.hashCode());
20 } catch (Exception e) {
21 e.printStackTrace();
22 } finally {
23 if (session != null) {
24 session.close();
25 }
26 }
27 }
控制台输出为:
1DEBUG 05-16 23:47:43,170 Cache Hit Ratio [cn.codefish.mybatis.dao.EmployeeDAO]: 0.0 (LoggingCache.java:62)
2DEBUG 05-16 23:47:43,184 ==> Preparing: SELECT * FROM employees where emp_no=?; (BaseJdbcLogger.java:159)
3DEBUG 05-16 23:47:43,227 ==> Parameters: 10022(Integer) (BaseJdbcLogger.java:159)
4DEBUG 05-16 23:47:43,256 <== Total: 1 (BaseJdbcLogger.java:159)
5e1 hashcode:1413623320
6DEBUG 05-16 23:47:43,396 Cache Hit Ratio [cn.codefish.mybatis.dao.EmployeeDAO]: 0.5 (LoggingCache.java:62)
7e2 hashcode:363023858
从控制台输出可以看出,只进行了一次sql查询,两次查询得到的对象是不同的。Cache Hit Ratio表示命中率,第一次查询时缓存中没有数据,所以命中率是0,第二次查询是从缓存中取的数据,所以命中率是50%.把session.close()方法改为session.commit(),可以达到同样的效果。
测试1:第一次查询后,不关闭session会怎么样 单元测试代码:
1@org.junit.Test
2 public void testSecondLevelCache() {
3 SqlSession session = null;
4 try {
5 session = sqlSessionFactory.openSession(true);// 获取session
6 EmployeeDAO dao = session.getMapper(EmployeeDAO.class);// 得到DAO
7 // 组装数据
8 Employee e = dao.getEmployeeByID(10022);
9 System.out.println("e1 hashcode:" + e.hashCode());
10 // session.close();// 关闭session
11 SqlSession session2 = sqlSessionFactory.openSession(true);// 再次获取session
12 dao = session2.getMapper(EmployeeDAO.class);// 再次得到DAO
13 Employee e2 = dao.getEmployeeByID(10022);
14 System.out.println("e2 hashcode:" + e2.hashCode());
15 session2.close();
16 } catch (Exception e) {
17 e.printStackTrace();
18 } finally {
19 if (session != null) {
20 session.close();
21 }
22 }
23 }
输出为:
1DEBUG 05-17 00:08:32,765 Cache Hit Ratio [cn.codefish.mybatis.dao.EmployeeDAO]: 0.0 (LoggingCache.java:62)
2DEBUG 05-17 00:08:32,794 ==> Preparing: SELECT * FROM employees where emp_no=?; (BaseJdbcLogger.java:159)
3DEBUG 05-17 00:08:32,826 ==> Parameters: 10022(Integer) (BaseJdbcLogger.java:159)
4DEBUG 05-17 00:08:32,865 <== Total: 1 (BaseJdbcLogger.java:159)
5e1 hashcode:1413623320
6DEBUG 05-17 00:08:32,867 Cache Hit Ratio [cn.codefish.mybatis.dao.EmployeeDAO]: 0.0 (LoggingCache.java:62)
7DEBUG 05-17 00:08:32,985 ==> Preparing: SELECT * FROM employees where emp_no=?; (BaseJdbcLogger.java:159)
8DEBUG 05-17 00:08:32,985 ==> Parameters: 10022(Integer) (BaseJdbcLogger.java:159)
9DEBUG 05-17 00:08:32,997 <== Total: 1 (BaseJdbcLogger.java:159)
10e2 hashcode:1976804832
从控制台输出可以看出,进行了两次sql查询,每一次的缓存命中率是0。只有当session会话提交或关闭后,数据才会转移到二级缓存,因为查询出来的数据默认会放进一级缓存,当第一次查询后,二级缓存依然是空的,所以第二次查询的命中率是0.
情况2:不使用缓存标签 注释掉二级缓存的cache配置:
1<!-- <cache eviction="LRU" flushInterval="6000" readOnly="false" size="1024" ></cache> -->
此时控制台输出为:
1DEBUG 05-17 00:25:09,217 ==> Preparing: SELECT * FROM employees where emp_no=?; (BaseJdbcLogger.java:159)
2DEBUG 05-17 00:25:09,249 ==> Parameters: 10022(Integer) (BaseJdbcLogger.java:159)
3DEBUG 05-17 00:25:09,303 <== Total: 1 (BaseJdbcLogger.java:159)
4e1 hashcode:355518265
5DEBUG 05-17 00:25:09,440 ==> Preparing: SELECT * FROM employees where emp_no=?; (BaseJdbcLogger.java:159)
6DEBUG 05-17 00:25:09,441 ==> Parameters: 10022(Integer) (BaseJdbcLogger.java:159)
7DEBUG 05-17 00:25:09,451 <== Total: 1 (BaseJdbcLogger.java:159)
8e2 hashcode:754177595
显然进行了二次查询。
和缓存相关的几个属性
cacheEnabled属性:
1<setting name="cacheEnabled" value="false"></setting>
将其设置为false,看一下单元测试代码,测一下一级缓存:
1@org.junit.Test
2 public void testSecondLevelCache() {
3 SqlSession session = null;
4 try {
5 session = sqlSessionFactory.openSession(true);// 获取session
6 EmployeeDAO dao = session.getMapper(EmployeeDAO.class);// 得到DAO
7 // 组装数据
8 Employee e = dao.getEmployeeByID(10022);
9 System.out.println("e1 hashcode:" + e.hashCode());
10 // session.close();// 关闭session
11 // session.commit();
12 // SqlSession session2 = sqlSessionFactory.openSession(true);//
13 // 再次获取session
14 // dao = session2.getMapper(EmployeeDAO.class);// 再次得到DAO
15 Employee e2 = dao.getEmployeeByID(10022);
16 System.out.println("e2 hashcode:" + e2.hashCode());
17 // session2.close();
18 } catch (Exception e) {
19 e.printStackTrace();
20 } finally {
21 if (session != null) {
22 session.close();
23 }
24 }
25 }
控制台输出:
1DEBUG 05-17 00:31:11,503 ==> Preparing: SELECT * FROM employees where emp_no=?; (BaseJdbcLogger.java:159)
2DEBUG 05-17 00:31:11,537 ==> Parameters: 10022(Integer) (BaseJdbcLogger.java:159)
3DEBUG 05-17 00:31:11,563 <== Total: 1 (BaseJdbcLogger.java:159)
4e1 hashcode:402009651
5e2 hashcode:402009651
控制台输出显示,只发送了一次sql,切e1和e2是同一个对象,所以一级缓存肯定生效了。取消session.commit()这一行注释,控制台输出为:
1DEBUG 05-17 00:35:36,348 ==> Preparing: SELECT * FROM employees where emp_no=?; (BaseJdbcLogger.java:159)
2DEBUG 05-17 00:35:36,381 ==> Parameters: 10022(Integer) (BaseJdbcLogger.java:159)
3DEBUG 05-17 00:35:36,413 <== Total: 1 (BaseJdbcLogger.java:159)
4e1 hashcode:402009651
5DEBUG 05-17 00:35:36,422 ==> Preparing: SELECT * FROM employees where emp_no=?; (BaseJdbcLogger.java:159)
6DEBUG 05-17 00:35:36,424 ==> Parameters: 10022(Integer) (BaseJdbcLogger.java:159)
7DEBUG 05-17 00:35:36,436 <== Total: 1 (BaseJdbcLogger.java:159)
8e2 hashcode:45023307
进行了两次查询,说明二级缓存并没有生效。说明cacheEnabled=false关闭了二级缓存,没有关闭一级缓存。
设置useCache属性为false:
1<select id="getEmployeeByID" resultType="cn.codefish.mybatis.dao.Employee" databaseId="mysql" useCache="false">
2 SELECT * FROM employees where emp_no=#{id};
3 </select>
做和上面相同的测试,结果也一样。useCache属性为false关闭了二级缓存,没有关闭一级缓存。
**flushCache=”true”:**每隔增删改语句都默认带有flushCache=”true”,所以每次执行之后都会清空缓存,一级缓存和二级缓存都会被清空。
**flushCache=”false”:**每条select语句都默认带有flushCache=”false”,所以不会自动清除缓存,如果改为true,则会清除缓存。
session.clearCache();会清除一级缓存,但是不会影响二级缓存。测试如下:
1@org.junit.Test
2 public void testSecondLevelCache() {
3 SqlSession session = null;
4 try {
5 session = sqlSessionFactory.openSession(true);// 获取session
6 EmployeeDAO dao = session.getMapper(EmployeeDAO.class);// 得到DAO
7 // 组装数据
8 Employee e = dao.getEmployeeByID(10022);
9 System.out.println("e1 hashcode:" + e.hashCode());
10 // session.close();// 关闭session
11 session.commit();
12 session.clearCache();
13 // SqlSession session2 = sqlSessionFactory.openSession(true);//
14 // 再次获取session
15 // dao = session2.getMapper(EmployeeDAO.class);// 再次得到DAO
16 Employee e2 = dao.getEmployeeByID(10022);
17 System.out.println("e2 hashcode:" + e2.hashCode());
18 // session2.close();
19 } catch (Exception e) {
20 e.printStackTrace();
21 } finally {
22 if (session != null) {
23 session.close();
24 }
25 }
26 }
控制台结果如下:
1DEBUG 05-17 00:53:42,305 Cache Hit Ratio [cn.codefish.mybatis.dao.EmployeeDAO]: 0.0 (LoggingCache.java:62)
2DEBUG 05-17 00:53:42,316 ==> Preparing: SELECT * FROM employees where emp_no=?; (BaseJdbcLogger.java:159)
3DEBUG 05-17 00:53:42,346 ==> Parameters: 10022(Integer) (BaseJdbcLogger.java:159)
4DEBUG 05-17 00:53:42,388 <== Total: 1 (BaseJdbcLogger.java:159)
5e1 hashcode:1413623320
6DEBUG 05-17 00:53:42,549 Cache Hit Ratio [cn.codefish.mybatis.dao.EmployeeDAO]: 0.5 (LoggingCache.java:62)
7e2 hashcode:363023858
MyBatis的缓存原理图
MyBatis缓存原理
MyBatis整合第三方缓存框架ehcache
首先引入相关jar包: ehcache_jars 或者通过maven引入:
1<dependency>
2 <groupId>org.mybatis.caches</groupId>
3 <artifactId>mybatis-ehcache</artifactId>
4 <version>1.1.0</version>
5</dependency>
6<dependency>
7 <groupId>org.slf4j</groupId>
8 <artifactId>slf4j-log4j12</artifactId>
9 <version>1.7.25</version>
10</dependency>
然后在配置cache,引入ehcache的类:
1<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
最后需要把ehcache.xml配置文件放在classpath路径下: ehcache.xml:
1<?xml version="1.0" encoding="UTF-8"?>
2<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
4 <!-- 磁盘保存路径 -->
5 <diskStore path="D:\44\ehcache" />
6
7 <defaultCache
8 maxElementsInMemory="1"
9 maxElementsOnDisk="10000000"
10 eternal="false"
11 overflowToDisk="true"
12 timeToIdleSeconds="120"
13 timeToLiveSeconds="120"
14 diskExpiryThreadIntervalSeconds="120"
15 memoryStoreEvictionPolicy="LRU">
16 </defaultCache>
17</ehcache>
18
19<!--
20属性说明:
21l diskStore:指定数据在磁盘中的存储位置。
22l defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用<defalutCache/>指定的的管理策略
23
24以下属性是必须的:
25l maxElementsInMemory - 在内存中缓存的element的最大数目
26l maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大
27l eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断
28l overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上
29
30以下属性是可选的:
31l timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大
32l timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
33 diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.
34l diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
35l diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作
36l memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)
37 -->
进行单元测试,看一下控制台的输出:
1DEBUG 05-17 23:55:15,875 Configuring ehcache from ehcache.xml found in the classpath: file:/home/dongzhi/workspace/sts/mybatis4/target/classes/ehcache.xml (ConfigurationFactory.java:132)
2DEBUG 05-17 23:55:15,879 Configuring ehcache from URL: file:/home/dongzhi/workspace/sts/mybatis4/target/classes/ehcache.xml (ConfigurationFactory.java:98)
3DEBUG 05-17 23:55:15,880 Configuring ehcache from InputStream (ConfigurationFactory.java:150)
4DEBUG 05-17 23:55:15,896 Ignoring ehcache attribute xmlns:xsi (BeanHandler.java:271)
5DEBUG 05-17 23:55:15,897 Ignoring ehcache attribute xsi:noNamespaceSchemaLocation (BeanHandler.java:271)
6DEBUG 05-17 23:55:15,899 Disk Store Path: . (DiskStoreConfiguration.java:141)
7DEBUG 05-17 23:55:15,914 Creating new CacheManager with default config (CacheManager.java:1036)
8DEBUG 05-17 23:55:15,919 propertiesString is null. (PropertyUtil.java:88)
9DEBUG 05-17 23:55:15,931 No CacheManagerEventListenerFactory class specified. Skipping... (ConfigurationHelper.java:185)
10DEBUG 05-17 23:55:15,949 No BootstrapCacheLoaderFactory class specified. Skipping... (Cache.java:955)
11DEBUG 05-17 23:55:15,949 CacheWriter factory not configured. Skipping... (Cache.java:929)
12DEBUG 05-17 23:55:15,954 No CacheExceptionHandlerFactory class specified. Skipping... (ConfigurationHelper.java:96)
13DEBUG 05-17 23:55:16,003 Initialized net.sf.ehcache.store.MemoryStore for cn.codefish.mybatis.dao.EmployeeDAO (MemoryStore.java:153)
14DEBUG 05-17 23:55:16,016 Using diskstore path . (DiskStorePathManager.java:169)
15DEBUG 05-17 23:55:16,017 Holding exclusive lock on /home/dongzhi/workspace/sts/mybatis4/./.ehcache-diskstore.lock (DiskStorePathManager.java:170)
16DEBUG 05-17 23:55:16,018 Failed to delete file cn%002ecodefish%002emybatis%002edao%002e%0045mployee%0044%0041%004f.index (DiskStorageFactory.java:860)
17DEBUG 05-17 23:55:16,028 Matching data file missing (or empty) for index file. Deleting index file ./cn%002ecodefish%002emybatis%002edao%002e%0045mployee%0044%0041%004f.index (DiskStorageFactory.java:168)
18DEBUG 05-17 23:55:16,029 Failed to delete file cn%002ecodefish%002emybatis%002edao%002e%0045mployee%0044%0041%004f.index (DiskStorageFactory.java:860)
19DEBUG 05-17 23:55:16,045 Initialised cache: cn.codefish.mybatis.dao.EmployeeDAO (Cache.java:1165)
20DEBUG 05-17 23:55:16,045 CacheDecoratorFactory not configured for defaultCache. Skipping for 'cn.codefish.mybatis.dao.EmployeeDAO'. (ConfigurationHelper.java:354)
21DEBUG 05-17 23:55:16,123 Cache Hit Ratio [cn.codefish.mybatis.dao.EmployeeDAO]: 0.0 (LoggingCache.java:62)
22DEBUG 05-17 23:55:16,133 ==> Preparing: SELECT * FROM employees where emp_no=?; (BaseJdbcLogger.java:159)
23DEBUG 05-17 23:55:16,163 ==> Parameters: 10022(Integer) (BaseJdbcLogger.java:159)
24DEBUG 05-17 23:55:16,203 <== Total: 1 (BaseJdbcLogger.java:159)
25e1 hashcode:1171434979
26DEBUG 05-17 23:55:16,207 put added 0 on heap (Segment.java:425)
27DEBUG 05-17 23:55:16,225 fault removed 0 from heap (Segment.java:779)
28DEBUG 05-17 23:55:16,225 fault added 0 on disk (Segment.java:796)
29DEBUG 05-17 23:55:16,227 Cache Hit Ratio [cn.codefish.mybatis.dao.EmployeeDAO]: 0.5 (LoggingCache.java:62)
30e2 hashcode:1171434979
控制台输出上显示了一堆。。。
小节
本次文章记录了MyBatis的缓存原理的学习,MyBatis的缓存分为一级缓存和二级缓存,二级缓存是全局缓存。数据在访问时,先去取二级缓存,只有当二级缓存取不到时,再去取一级缓存。一级缓存也取不到时,才去访问数据库。另外,MyBatis自带的缓存比较薄弱,但是它对外提供了Cache接口,可以与第三方缓存框架ehcache进行整合,提高查询效率。