# MyBatis教程 - 12 缓存
什么是缓存?
就是 MyBatis 查询出数据,会将数据进行缓存,这样再次执行相同的查询,就直接获取缓存的数据,不用再去数据库中查询,如果缓存中没有,再去数据库中查询,减少数据库查询次数,从而提高查询效率。
在 MyBatis 中,缓存分为一级缓存和二级缓存 :
- 一级缓存:一级缓存是 SqlSession 级别的缓存,也就是说,在同一个 SqlSession 期间,如果执行相同的查询,MyBatis 会优先从缓存中获取数据,而不会再次访问数据库。一级缓存默认是开启的。
- 二级缓存:二级缓存是 SqlSessionFactory 级别的缓存。多个 SqlSession 共享二级缓存,缓存的数据可以被不同的 SqlSession 访问。二级缓存默认是关闭的,需要在 Mapper 文件或全局配置文件中显式开启。
# 12.1 一级缓存
# 1 测试一级缓存
一级缓存默认开启,我们直接测试一下查询用户即可。
@Test
public void testSelectUserById() {
// 获取SqlSession连接
SqlSession sqlSession = MyBatisUtils.getSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
Integer id = 1;
// 查询用户列表
User user1 = userMapper.selectById(id);
log.info("user1: {}", user1);
User user2 = userMapper.selectById(id);
log.info("user2: {}", user2);
// 关闭SqlSession
sqlSession.close();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
上面的代码,执行了 2 次 userMapper.selectById
查询了两次用户。但是查看日志,发下只执行了一次 sql 。
执行如下:
同样,获取使用 SqlSession
获取两次 Mapper,执行查询,也是只有一条 sql,因为都是使用同一个 SqlSession
,如下:
@Test
public void testSelectUserById() {
// 获取SqlSession连接
SqlSession sqlSession = MyBatisUtils.getSession();
Integer id = 1;
// 获取session
UserMapper userMapper1 = sqlSession.getMapper(UserMapper.class);
// 查询用户列表
User user1 = userMapper1.selectById(id);
log.info("user1: {}", user1);
// 获取session
UserMapper userMapper2 = sqlSession.getMapper(UserMapper.class);
User user2 = userMapper2.selectById(id);
log.info("user2: {}", user2);
// 关闭SqlSession
sqlSession.close();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
上面获取了两次 UserMapper ,但是还是执行一次 SQL。
但是使用两个 SqlSession,就会执行两次 SQL,测试代码如下 :
@Test
public void testSelectUserById() {
Integer id = 1;
// 获取SqlSession连接
SqlSession sqlSession1 = MyBatisUtils.getSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
// 查询用户列表
User user1 = userMapper1.selectById(id);
log.info("user1: {}", user1);
// 获取SqlSession连接
SqlSession sqlSession2 = MyBatisUtils.getSession();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
User user2 = userMapper2.selectById(id);
log.info("user2: {}", user2);
// 关闭SqlSession
sqlSession1.close();
sqlSession2.close();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
上面使用了两个不同的 SqlSession,所以会执行两次 SQL。
# 2 一级缓存失效的情况
一级缓存失效有4中情况:
- 使用不同的 SqlSession,不同的 SqlSession 对应不同的一级缓存;
- 同一个 SqlSession,但是查询参数或查询条件不同;
- 同一个 SqlSession,在两次相同的查询期间,执行了增删改操作;
- 同一个 SqlSession,在两次相同的查询期间,手动清空了缓存,可以使用
sqlSession.clearCache();
清空缓存。
# 12.2 二级缓存
二级缓存是 SqlSessionFactory 级别的缓存,同一个 SqlSessionFactory 创建的多个 SqlSession, 之间也可以共享缓存数据。
# 1 开启二级缓存
二级缓存默认是关闭的,需要通过如下步骤开启。
- 在 MyBatis 全局配置文件(
mybatis-config.xml
)中启用二级缓存:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
2
3
上面默认是开启的,所以可以不用配置。
- 在 Mapper 映射文件中通过
<cache>
标签开启二级缓存:
<mapper namespace="com.foooor.mybatis.mapper.UserMapper">
<!-- 开启二级缓存 -->
<cache/>
<select id="selectById" resultType="User">
SELECT * FROM tb_user WHERE id = #{id}
</select>
</mapper>
2
3
4
5
6
7
8
9
在 <cache>
标签中,你可以配置不同的选项来控制缓存行为:
<cache
eviction="LRU"
flushInterval="60000"
size="512"
readOnly="true"/>
2
3
4
5
eviction
:指定缓存的逐出策略(删除缓存数据的策略)。可选值有:
LRU
(默认值):最近最少使用的缓存项将被清除。FIFO
:按缓存项进入缓存的先后顺序来逐出缓存。SOFT
:采用软引用的策略来存储缓存项,当内存不足时,会自动清除一些缓存项。WEAK
:采用弱引用的策略来存储缓存项。
flushInterval
:指定缓存刷新间隔时间(单位为毫秒)。默认不刷新,即缓存会一直保留,直到手动清理。
size
:设置缓存的最大条目数。超过此大小后,将根据逐出策略清除缓存。
readOnly
:指定缓存是否为只读。只读缓存将提高并发访问性能,但一旦缓存的数据被修改,所有线程将共享这个变化。默认值为 false
。
- 查询数据所转换的实体类,必须实现序列号接口
// 必须实现Serializable接口
public class User implements java.io.Serializable {
// ...略
}
2
3
4
- 二级缓存必须在 SqlSession提交或关闭之后才会生效。
这个待会演示一下。
# 2 测试二级缓存
还是查询用户,为了
package com.foooor.mybatis.test;
import com.foooor.mybatis.mapper.UserMapper;
import com.foooor.mybatis.pojo.User;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
@Slf4j
public class MyBatisTest {
@Test
public void testSelectUserById() {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 获取SqlSession连接
SqlSession sqlSession1 = sqlSessionFactory.openSession();
// 又获取一个SqlSession连接
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
// 查询用户列表
User user1 = userMapper1.selectById(1);
log.info("user1: {}", user1);
// 关闭sqlSession
sqlSession1.close();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
User user2 = userMapper2.selectById(1);
log.info("user2: {}", user2);
sqlSession2.close();
}
/**
* 获取sqlSessionFactory
*/
public SqlSessionFactory getSqlSessionFactory() {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
return sqlSessionFactory;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
在上面的代码中,通过 SqlSessionFactory
获取到两个 SqlSession
,然后通过 SqlSession
获取到 Mapper 执行查询。但是只执行了一句 SQL,这里需要注意,sqlSession1
关闭以后,缓存才生效,sqlSession2 查询才会使用到缓存。
# 3 二级缓存失效的情况
在两次查询之间执行了增删改,导致数据变化,那么一级缓存和二级缓存会同时失效。
# 4 MyBatis缓存查询的顺序
- 首先查询二级缓存,因为二级缓存有其他 SqlSession 中查询出来的数据,可以直接使用;
- 如果二级缓存没有命中(没查询到),会再查询一级缓存;
- 如果一级缓存也没有命中,会查询数据库;
- SqlSession 关闭之后,一级缓存中的数据会写入到二级缓存中。