本篇文章记录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. 如果会话关闭,一级会话中的数据会被放在二级缓存中。新的会话查询信息,就可以参考二级缓存。

使用步骤:

  • 开启全局二级缓存配置。即在全局配置文件中配置:
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那么还要根据timeToIdleSecondstimeToLiveSeconds判断
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进行整合,提高查询效率。