mybatis 也提供了对缓存的支持, 分为一级缓存和二级缓存。 但是在默认的情况下, 只开启一级缓存(一级缓存是对同一个 SqlSession 而言的)。
一级缓存
同一个
SqlSession
对象, 在参数和 SQL 完全一样的情况先, 只执行一次 SQL 语句(如果缓存没有过期)也就是只有在参数和 SQL 完全一样的情况下, 才会有这种情况。
同一个sqlsession
@Test public void oneSqlSession() { SqlSession sqlSession = null; try { sqlSession = sqlSessionFactory.openSession(); StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); // 执行第一次查询 List<Student> students = studentMapper.selectAll(); for (int i = 0; i < students.size(); i++) { System.out.println(students.get(i)); } System.out.println("=============开始同一个 Sqlsession 的第二次查询============"); // 同一个 sqlSession 进行第二次查询 List<Student> stus = studentMapper.selectAll(); Assert.assertEquals(students, stus); for (int i = 0; i < stus.size(); i++) { System.out.println("stus:" + stus.get(i)); } } catch (Exception e) { e.printStackTrace(); } finally { if (sqlSession != null) { sqlSession.close(); } } }
在以上的代码中, 进行了两次查询, 使用相同的
SqlSession
, 结果如下
在日志和输出中:
第一次查询发送了 SQL 语句, 后返回了结果;
第二次查询没有发送 SQL 语句, 直接从内存中获取了结果。
而且两次结果输入一致, 同时断言两个对象相同也通过。
不同的sqlsession
@Test public void differSqlSession() { SqlSession sqlSession = null; SqlSession sqlSession2 = null; try { sqlSession = sqlSessionFactory.openSession(); StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); // 执行第一次查询 List<Student> students = studentMapper.selectAll(); for (int i = 0; i < students.size(); i++) { System.out.println(students.get(i)); } System.out.println("=============开始不同 Sqlsession 的第二次查询============"); // 从新创建一个 sqlSession2 进行第二次查询 sqlSession2 = sqlSessionFactory.openSession(); StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class); List<Student> stus = studentMapper2.selectAll(); // 不相等 Assert.assertNotEquals(students, stus); for (int i = 0; i < stus.size(); i++) { System.out.println("stus:" + stus.get(i)); } } catch (Exception e) { e.printStackTrace(); } finally { if (sqlSession != null) { sqlSession.close(); } if (sqlSession2 != null) { sqlSession2.close(); } } }
在代码中, 分别使用 sqlSession 和 sqlSession2 进行了相同的查询。
其结果如下
从日志中可以看到两次查询都分别从数据库中取出了数据。 虽然结果相同, 但两个是不同的对象。
刷新缓存是清空这个 SqlSession 的所有缓存, 不单单是某个键。
@Test public void sameSqlSessionNoCache() { SqlSession sqlSession = null; try { sqlSession = sqlSessionFactory.openSession(); StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); // 执行第一次查询 Student student = studentMapper.selectByPrimaryKey(1); System.out.println("=============开始同一个 Sqlsession 的第二次查询============"); // 同一个 sqlSession 进行第二次查询 Student stu = studentMapper.selectByPrimaryKey(1); Assert.assertEquals(student, stu); } catch (Exception e) { e.printStackTrace(); } finally { if (sqlSession != null) { sqlSession.close(); } } }
如果是以上, 没什么不同, 结果还是第二个不发 SQL 语句。
在此, 做一些修改, 在 StudentMapper.xml 中, 添加
flushCache=“true”
<select id="selectByPrimaryKey" flushCache="true" parameterType="java.lang.Integer" resultMap="BaseResultMap"> select <include refid="Base_Column_List" /> from student where student_id=#{id, jdbcType=INTEGER} </select>
第一次, 第二次都发送了 SQL 语句, 同时, 断言两个对象相同出错。
1.4 总结
在同一个 SqlSession 中, Mybatis 会把执行的方法和参数通过算法生成缓存的键值, 将键值和结果存放在一个 Map 中, 如果后续的键值一样, 则直接从 Map 中获取数据;不同的 SqlSession 之间的缓存是相互隔离的;
用一个 SqlSession, 可以通过配置使得在查询前清空缓存;
任何的 UPDATE, INSERT, DELETE 语句都会清空缓存。
二级缓存
二级缓存存在于 SqlSessionFactory 生命周期中。mapper级别的缓存,在同一个mapper之间共用
2.1 配置二级缓存
2.1.1 全局开关
在 mybatis 中, 二级缓存有全局开关和分开关, 全局开关, 在 mybatis-config.xml 中如下配置:<settings> <!--全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存。 --> <setting name="cacheEnabled" value="true"/> </settings>
默认是为 true, 即默认开启总开关。2.1.2 分开关
分开关就是说在 *Mapper.xml 中开启或关闭二级缓存, 默认是不开启的。2.1.3 entity 实现序列化接口public class Student implements Serializable { private static final long serialVersionUID = -4852658907724408209L; ... }
2.2 使用二级缓存@Test public void secendLevelCacheTest() { // 获取 SqlSession 对象 SqlSession sqlSession = sqlSessionFactory.openSession(); // 获取 Mapper 对象 StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); // 使用 Mapper 接口的对应方法,查询 id=2 的对象 Student student = studentMapper.selectByPrimaryKey(2); // 更新对象的名称 student.setName("奶茶"); // 再次使用相同的 SqlSession 查询id=2 的对象 Student student1 = studentMapper.selectByPrimaryKey(2); Assert.assertEquals("奶茶", student1.getName()); // 同一个 SqlSession , 此时是一级缓存在作用, 两个对象相同 Assert.assertEquals(student, student1); sqlSession.close(); SqlSession sqlSession1 = sqlSessionFactory.openSession(); StudentMapper studentMapper1 = sqlSession1.getMapper(StudentMapper.class); Student student2 = studentMapper1.selectByPrimaryKey(2); Student student3 = studentMapper1.selectByPrimaryKey(2); // 由于我们配置的 readOnly="true", 因此后续同一个 SqlSession 的对象都不一样 Assert.assertEquals("奶茶", student2.getName()); Assert.assertNotEquals(student3, student2); sqlSession1.close(); }
结果如下:2018-09-29 23:14:26,889 [main] DEBUG [org.apache.ibatis.datasource.pooled.PooledDataSource] - Created connection 242282810. 2018-09-29 23:14:26,889 [main] DEBUG [org.apache.ibatis.transaction.jdbc.JdbcTransaction] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@e70f13a] 2018-09-29 23:14:26,897 [main] DEBUG [com.homejim.mybatis.mapper.StudentMapper.selectByPrimaryKey] - ==> Preparing: select student_id, name, phone, email, sex, locked, gmt_created, gmt_modified from student where student_id=? 2018-09-29 23:14:26,999 [main] DEBUG [com.homejim.mybatis.mapper.StudentMapper.selectByPrimaryKey] - ==> Parameters: 2(Integer) 2018-09-29 23:14:27,085 [main] TRACE [com.homejim.mybatis.mapper.StudentMapper.selectByPrimaryKey] - <== Columns: student_id, name, phone, email, sex, locked, gmt_created, gmt_modified 2018-09-29 23:14:27,085 [main] TRACE [com.homejim.mybatis.mapper.StudentMapper.selectByPrimaryKey] - <== Row: 2, 小丽, 13821378271, xiaoli@mybatis.cn, 0, 0, 2018-09-04 18:27:42.0, 2018-09-04 18:27:42.0 2018-09-29 23:14:27,093 [main] DEBUG [com.homejim.mybatis.mapper.StudentMapper.selectByPrimaryKey] - <== Total: 1 2018-09-29 23:14:27,093 [main] DEBUG [com.homejim.mybatis.mapper.StudentMapper] - Cache Hit Ratio [com.homejim.mybatis.mapper.StudentMapper]: 0.0 2018-09-29 23:14:27,108 [main] DEBUG [org.apache.ibatis.transaction.jdbc.JdbcTransaction] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@e70f13a] 2018-09-29 23:14:27,116 [main] DEBUG [org.apache.ibatis.transaction.jdbc.JdbcTransaction] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@e70f13a] 2018-09-29 23:14:27,116 [main] DEBUG [org.apache.ibatis.datasource.pooled.PooledDataSource] - Returned connection 242282810 to pool. 2018-09-29 23:14:27,124 [main] DEBUG [com.homejim.mybatis.mapper.StudentMapper] - Cache Hit Ratio [com.homejim.mybatis.mapper.StudentMapper]: 0.3333333333333333 2018-09-29 23:14:27,124 [main] DEBUG [com.homejim.mybatis.mapper.StudentMapper] - Cache Hit Ratio [com.homejim.mybatis.mapper.StudentMapper]: 0.5
以上结果, 分几个过程解释:第一阶段:
在第一个 SqlSession 中, 查询出 student 对象, 此时发送了 SQL 语句;
student更改了name 属性;
SqlSession 再次查询出 student1 对象, 此时不发送 SQL 语句, 日志中打印了 「Cache Hit Ratio」, 代表二级缓存使用了, 但是没有命中。 因为一级缓存先作用了。
由于是一级缓存, 因此, 此时两个对象是相同的。
调用了 sqlSession.close(), 此时将数据序列化并保持到二级缓存中。
第二阶段:新创建一个 sqlSession对象;
查询出 student2 对象,直接从二级缓存中拿了数据, 因此没有发送 SQL 语句, 此时查了 3 个对象,但只有一个命中, 因此 命中率 1/3=0.333333;
查询出 student3 对象,直接从二级缓存中拿了数据, 因此没有发送 SQL 语句, 此时查了 4 个对象,但只有一个命中, 因此 命中率 2/4=0.5;
由于 readOnly=“true”, 因此 student2 和 student3 都是反序列化得到的, 为不同的实例。