# 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();
}
1
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();
}
1
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();
}
1
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 开启二级缓存

二级缓存默认是关闭的,需要通过如下步骤开启。

  1. 在 MyBatis 全局配置文件(mybatis-config.xml)中启用二级缓存:
<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>
1
2
3

上面默认是开启的,所以可以不用配置。


  1. 在 Mapper 映射文件中通过 <cache> 标签开启二级缓存:
<mapper namespace="com.foooor.mybatis.mapper.UserMapper">
    <!-- 开启二级缓存 -->
    <cache/>

    <select id="selectById" resultType="User">
        SELECT * FROM tb_user WHERE id = #{id}
    </select>

</mapper>
1
2
3
4
5
6
7
8
9

<cache> 标签中,你可以配置不同的选项来控制缓存行为:

<cache
    eviction="LRU"
    flushInterval="60000"
    size="512"
    readOnly="true"/>
1
2
3
4
5

eviction:指定缓存的逐出策略(删除缓存数据的策略)。可选值有:

  • LRU(默认值):最近最少使用的缓存项将被清除。
  • FIFO:按缓存项进入缓存的先后顺序来逐出缓存。
  • SOFT:采用软引用的策略来存储缓存项,当内存不足时,会自动清除一些缓存项。
  • WEAK:采用弱引用的策略来存储缓存项。

flushInterval:指定缓存刷新间隔时间(单位为毫秒)。默认不刷新,即缓存会一直保留,直到手动清理。

size:设置缓存的最大条目数。超过此大小后,将根据逐出策略清除缓存。

readOnly:指定缓存是否为只读。只读缓存将提高并发访问性能,但一旦缓存的数据被修改,所有线程将共享这个变化。默认值为 false

  1. 查询数据所转换的实体类,必须实现序列号接口
// 必须实现Serializable接口
public class User implements java.io.Serializable {
    // ...略
}
1
2
3
4
  1. 二级缓存必须在 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;
        }
    }

}
1
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缓存查询的顺序

  1. 首先查询二级缓存,因为二级缓存有其他 SqlSession 中查询出来的数据,可以直接使用;
  2. 如果二级缓存没有命中(没查询到),会再查询一级缓存;
  3. 如果一级缓存也没有命中,会查询数据库;
  4. SqlSession 关闭之后,一级缓存中的数据会写入到二级缓存中。