简介
mybatis包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大地提升查询效率。
Mybatis系统中默认定义了两级缓存。一级缓存和二级缓存。
- 默认情况下,只有一级缓存(SqlSession级别地缓存,也成为本地缓存)开启
- 二级缓存需要手动开启和配置,它是基于namespace级别地缓存
- 为了提高扩展性,mybatis定义了缓存接口Cache,我们可以通过实现Cache接口来定义二级缓存。
一级缓存
又称本地缓存,与数据库同一次会话期间查询道德数据会放在本地缓存中。以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库。
测试:
@Test
public void test01() throws IOException {
//1.根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession session = sqlSessionFactory.openSession();
try {
EmployeeMapper employeeMapper= session.getMapper(EmployeeMapper.class);
Employee emp = employeeMapper.getEmpById(1);
System.out.println(emp);
Employee emp2 = employeeMapper.getEmpById(1);
System.out.println(emp2);
System.out.println(emp==emp2);
} finally {
session.close();
}
}
private SqlSessionFactory getSqlSessionFactory() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}
发现只查询了一次数据库。并且第二次查询和第一次查询得到地对象是同一个。
一级缓存失效情况
sqlSession不同
@Test
public void test01() throws IOException {
//1.根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession session = sqlSessionFactory.openSession();
SqlSession session2 = sqlSessionFactory.openSession();
try {
EmployeeMapper employeeMapper= session.getMapper(EmployeeMapper.class);
Employee emp = employeeMapper.getEmpById(1);
System.out.println(emp);
EmployeeMapper employeeMapper2 = session2.getMapper(EmployeeMapper.class);
Employee emp2 = employeeMapper2.getEmpById(1);
System.out.println(emp2);
System.out.println(emp==emp2);
} finally {
session.close();
}
}
查询条件不同
原因:一级缓存中不存在该缓存
@Test
public void test01() throws IOException {
//1.根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession session = sqlSessionFactory.openSession();
try {
EmployeeMapper employeeMapper= session.getMapper(EmployeeMapper.class);
Employee emp = employeeMapper.getEmpById(1);
System.out.println(emp);
Employee emp2 = employeeMapper.getEmpById(2);
System.out.println(emp2);
System.out.println(emp==emp2);
} finally {
session.close();
}
}
增删改
sqlSession相同,但是两次查询之间执行了增删改操作(尽管没有影响到原来缓存的数据)
@Test
public void test01() throws IOException {
//1.根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession session = sqlSessionFactory.openSession();
try {
EmployeeMapper employeeMapper= session.getMapper(EmployeeMapper.class);
Employee emp = employeeMapper.getEmpById(1);
System.out.println(emp);
employeeMapper.delEmpById(5);
Employee emp2 = employeeMapper.getEmpById(2);
System.out.println(emp2);
System.out.println(emp==emp2);
} finally {
session.close();
}
}
手动清空缓存
@Test
public void test01() throws IOException {
//1.根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession session = sqlSessionFactory.openSession();
try {
EmployeeMapper employeeMapper= session.getMapper(EmployeeMapper.class);
Employee emp = employeeMapper.getEmpById(1);
System.out.println(emp);
session.clearCache();
Employee emp2 = employeeMapper.getEmpById(2);
System.out.println(emp2);
System.out.println(emp==emp2);
} finally {
session.close();
}
}
二级缓存
基于namespace级别的缓存,一个namespace对应一个二级缓存。
工作机制:
-
一个会话,查询一条数据,这个数据就会被放到当前会话的一级缓存中
-
只有会话关闭,一级缓存中的数据才会被转移到二级缓存中,新的会话查询信息,就可以参照二级缓存中的内容
-
sqlSession=>EmployeeMapper=>Employee
不同namespace查出的数据会放在自己对应的缓存中(map)
使用:
1.开启全局二级缓存配置,在Mybatis全局配置文件中配置
注意:该配置控制的是二级缓存,对一级缓存没有影响
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
2.在sql映射文件配置使用二级缓存
<mapper namespace="com.wj.dao.EmployeeMapper">
<cache eviction="FIFO"
flushInterval="6000"
readOnly="false"
size="1024"/>
....
</mapper>
常用配置项说明:
eviction:缓存的回收策略
- LRU:最近最少使用(默认使用)
- FIFO:先进先出
- SOFT:软引用:基于垃圾回收器状态和软引用规则的对象
- WEAK:弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象
flushInterval:缓存刷新间隔,默认不清空,设置毫秒值
readOnly:缓存是否只读
- true:只读,mybatis认为所以从缓存中获取数据的操作都是只读操作,不会修改数据,mybatis为了较快获取速度,直接就会将数据缓存中的引用交给用户,不安全但速度快。
- false:非只读,mybatis觉得获取的数据可能会被修改,mybatis会利用序列化&反序列化技术克隆一份新的数据给用户,安全(默认)
size:缓存存放多少元素
type:指定自定义缓存的全类型,需要实现Cache接口即可。
3.我们的实体类需要实现序列化接口
@Data
@ToString
public class Employee implements Serializable {
private Integer id;
private String lastName;
private String email;
private String gender;
}
测试:
@Test
public void testSecondLevel() throws IOException {
//1.根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession session = sqlSessionFactory.openSession();
SqlSession session2 = sqlSessionFactory.openSession();
try {
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
EmployeeMapper employeeMapper2 = session2.getMapper(EmployeeMapper.class);
Employee emp = employeeMapper.getEmpById(1);
System.out.println(emp);
session.close();
System.out.println("===============关闭session================");
Employee emp2 = employeeMapper2.getEmpById(1);
System.out.println(emp2);
session2.close();
}finally {
}
}
注意:
只有之前的sqlSession关闭了,才有二级缓存,否则不会进入二级缓存。
缓存相关属性/设置
1.cacheEnabled=false 二级缓存关闭,一级缓存仍有用
2.select标签的 useCache=false 二级缓存关闭,一级缓存仍有用
3.每个增删改标签的 flushCache=true 增删改完成之后,二级缓存和一级缓存同时清空
查询标签 flushCache=true 每次查询之后都会清空缓存
4.sqlSession.clearCache() 只是清楚当前session的一级缓存
5.localCacheScope:本地缓存作用域
- SESSION:当前会话的所有数据保存在会话缓存中
- STATEMENT:可以禁用一级缓存
查询顺序
二级缓存=》一级缓存=》数据库
缓存创建部分源码
跟缓存有关的核心接口:org.apache.ibatis.cache.Cache.java
光从包名decorators,我们就只可以得知,这Cache的设计就是装饰器模式。
查看PerpetualCache代码,平平无奇!!
public class PerpetualCache implements Cache {
private String id;
private Map<Object, Object> cache = new HashMap<Object, Object>();
public PerpetualCache(String id) {
this.id = id;
}
.......
我们点进decorators包下面任意一个Cache实现:例如BlockingCache
典型的装饰器模式的结构。该BlockingCache组合了另一个Cache,我们猜测它是装饰者,而BlockingCache是具体组件。
在XMLMapperBuilder中:会根据sql映射文件中cache标签创建一个Cache,参数是标签的配置属性。
点击进入MapperBuilderAssistant的useNewCache方法:
点击进入CacheBuilder的build()方法
点击进入newCacheDecoratorInstance方法:base是Cache的具体实现,默认是PerpetualCache
既然是通过反射进行装饰的,那我们可以任意点击一个Cache的装饰类,查看它的构造器,还以BlockingCache为例
而mybatis的缓存模块,也是装饰器模式的典型代表,学习楷模!!!