MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。
MyBatis系统中默认定义了两级缓存。
一级缓存(本地缓存)
与数据库同一次会话期间查询到的数据会放在本地缓存中,以后如果需要获取相同的数据,直接从缓存中取,没必要再去查询数据库。一级缓存是sqlSession级别的缓存,默认一直是开启的。
@Test
public void testFirstLevelCache() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
List<Employee> employees = mapper.getEmpByLastName("hello");
for (Employee employee : employees) {
System.out.println(employee);
}
List<Employee> employees2 = mapper.getEmpByLastName("hello");
for (Employee employee : employees2) {
System.out.println(employee);
}
}finally {
sqlSession.close();
}
}
上面例子中,根据名称查询员工,调用两次getEmpByLastName方法,由于sqlsession本地缓存的存在,第二次并没有调用方法去查数据库,而是直接从缓存中获取。
- 一级缓存的失效情况
一级缓存失效需要重新向数据库发出查询。
1、sqlSession不同
@Test
public void testFirstLevelCache() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
try {
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
List<Employee> employees = mapper.getEmpByLastName("hello");
for (Employee employee : employees) {
System.out.println(employee);
}
EmployeeMapper mapper2 = sqlSession2.getMapper(EmployeeMapper.class);
List<Employee> employees2 = mapper2.getEmpByLastName("hello");
for (Employee employee : employees2) {
System.out.println(employee);
}
}finally {
sqlSession.close();
sqlSession2.close();
}
}
使用不同的sqlSession对象,尽管两次查询是一样的,第二次查询还是会调用方法,执行sql,查询数据库。
2、sqlSession相同,查询条件不同(当前一级缓存中还没有这个数据)
3、sqlSession相同,两个查询之间执行了增删改操作(增删改可能对当前数据有影响,保证数据一致性缓存失效)
@Test
public void testFirstLevelCache() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
List<Employee> employees = mapper.getEmpByLastName("hello");
for (Employee employee : employees) {
System.out.println(employee);
}
mapper.addEmp(new Employee(null, "testCache", "cache", "1"));
System.out.println("数据添加成功");
EmployeeMapper mapper2 = sqlSession.getMapper(EmployeeMapper.class);
List<Employee> employees2 = mapper2.getEmpByLastName("hello");
for (Employee employee : employees2) {
System.out.println(employee);
}
}finally {
sqlSession.close();
}
}
相同的sqlSession,在两次相同的查询中间执行增加数据的操作。
4、sqlSession相同,手动清除了一级缓存
二级缓存(全局缓存)
基于namespace级别的缓存,一个namespace对应一个二级缓存。
- 工作机制:
1、一个会话,查询一条数据,这个数据就会被放在当前的一级缓存中。
2、如果会话关闭,一级缓存中的数据会被保存到二级缓存中,新的会话查询信息,就可以参照二级缓存
3、sqlSession即通过EmployeeMapper查询Employee,也通过DepartmentMapper查询Department,不同namespace查出的数据会放在自己对应的缓存中(map)。 - 使用
使用步骤:
1、开启全局二级缓存配置
cacheEnabled 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。
2、去mapper.xml中配置使用二级缓存
cache的一些默认规则:
eviction:缓存回收策略:
—— LRU – 最近最少使用的:移除最长时间不被使用的对象。
——FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
——SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
—— WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
默认的是 LRU。
flushInterval:刷新间隔,单位毫秒,默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
size:引用数目,正整数,代表缓存最多可以存储多少个对象,太大容易导致内存溢出
readOnly:只读,true/false
—— true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。mybatis认为所有从缓存中获取的数据都是只读操作
——false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false。
3、我们的POJO需要实现序列化接口Serializable
效果:数据会从二级缓存中获取,查出的数据都会被默认先放在一级缓存中,只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中。
@Test
public void testSecondLevelCache() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
try {
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
EmployeeMapper mapper2 = sqlSession2.getMapper(EmployeeMapper.class);
List<Employee> employees1 = mapper.getEmpByLastName("hello");
for (Employee employee : employees1) {
System.out.println(employee);
}
//只有会话关闭后,二级缓存中才能有数据
sqlSession.close();
List<Employee> employees2 = mapper2.getEmpByLastName("hello");
for (Employee employee : employees2) {
System.out.println(employee);
}
}finally {
sqlSession.close();
sqlSession2.close();
}
}
和缓存有关的设置以及属性
- cacheEnabled:默认值true,如果设置为false,二级缓存关闭,一级缓存一直可用。
- useCache:每个select标签都有useCache = “true”,如果设置为false,二级缓存关闭,一级缓存依然可以使用。
- flushCache:select语句默认值为false,增删改语句默认flushCache = “true”,增删改执行完后就会清除缓存。测试:在两次相同条件的查询语句中间加入增删改操作,一级缓存和二级缓存都会被清空。
- sqlSession.clearCache():只是清除当前sqlSession的一级缓存
- localCacheScope:(本地缓存作用域)MyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT(禁用缓存),本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。
缓存的原理
一级缓存是一次会话范围内,相对于sqlSession,不同的sqlSession有不同的缓存区,范围较小。二级缓存一个namespace范围内,不同的mapper.xml有不同的缓存区,新会话进入会先找二级缓存,如果二级缓存没有数据去找一级缓存,一级缓存也没有数据就去执行sql查询数据库。
第三方缓存整合(ehcache)
1、先导入ehcache的核心包
2、导入ehcache与mybatis的适配包(可从官方网站下载),slf4j日志包。
3、在mapper.xml中使用自定义缓存
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>