一级缓存

MyBatis 是一个优秀的持久层框架,它通过提供 SQL 映射和对象关系映射功能简化了数据库操作。其中,MyBatis 的缓存机制是其重要的性能优化功能之一,分为一级缓存和二级缓存。一级缓存是默认开启的,且对开发者透明。

一级缓存是 MyBatis 中的本地缓存,作用范围是 SqlSession。在同一个 SqlSession 中执行的多次相同的查询操作,如果参数和 SQL 语句相同,MyBatis 会从缓存中直接返回查询结果,而不会重复访问数据库,从而提高了性能。

代码测试

测试一:同一 SqlSession 中两次相同查询

public class WzkicuCache01 {

    public static void main(String[] args) throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
                .build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<WzkUser> wzkUser = userMapper.findAll();
        System.out.println(wzkUser);
        List<WzkUser> wzkUser2 = userMapper.findAll();
        System.out.println(wzkUser2);
        sqlSession.close();
    }

}

运行结果

24/11/13 10:12:08 DEBUG UserMapper.findAll: ==>  Preparing: select *,o.id oid from wzk_user u left join wzk_orders o on u.id=o.uid;
24/11/13 10:12:08 DEBUG UserMapper.findAll: ==> Parameters:
24/11/13 10:12:08 DEBUG UserMapper.findAll: <==      Total: 3

两次查询中间没有执行 SQL,说明使用了 MyBatis 的一级缓存。

测试二:查询后执行更新操作

public class WzkicuCache02 {

    public static void main(String[] args) throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
                .build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        // 第一次查询
        List<WzkUser> wzkUser = userMapper.findAll();
        System.out.println(wzkUser);
        // 加入一次 UPDATE
        WzkUser wzkUserUpdate = WzkUser
                .builder()
                .id(1)
                .username("wzk-update")
                .password("123-update")
                .build();
        wzkUserUpdate.setId(1);
        userMapper.updateById(wzkUserUpdate);
        sqlSession.commit();
        // 再次查询
        List<WzkUser> wzkUser2 = userMapper.findAll();
        System.out.println(wzkUser2);
        sqlSession.close();
    }

}

运行结果

运行结果为:SQL 查询 → 更新操作 → SQL 查询。UPDATE 之后,MyBatis 会清空缓存。

工作原理

一级缓存存储在 SqlSession 的内部,它基于 Java 的 HashMap 结构实现,使用查询的 SQL 语句和参数作为键,查询结果作为值。

工作流程

  1. 查询前检查缓存:SqlSession 会先检查缓存中是否已经存在相应的数据。如果缓存命中,则直接返回缓存中的结果,不再执行 SQL 语句。

  2. 查询后更新缓存:如果缓存未命中,MyBatis 会执行 SQL 查询,获取结果,并将结果存储在一级缓存中。

  3. 缓存的失效:在某些情况下,一级缓存会失效。

一级缓存的特点

  • 作用范围:仅限于当前的 SqlSession
  • 线程安全:SqlSession 是线程不安全的,因此一级缓存仅在单个线程中有效
  • 生命周期:一级缓存的生命周期与 SqlSession 一致,当 SqlSession 被关闭或销毁时,缓存会被清空
  • 缓存粒度:一级缓存的粒度较细,限定在单个 SqlSession

一级缓存的生效条件

  • 相同的 SQL 语句:查询的 SQL 和参数必须完全相同
  • 相同的 SqlSession:必须在同一个 SqlSession 实例中进行查询操作
  • 没有执行过更新操作:在执行 INSERT、UPDATE 或 DELETE 操作后,一级缓存会被清空
  • 未显式清空缓存:如果手动调用了 clearCache() 方法,一级缓存会被清空

一级缓存的失效场景

  • 不同的 SqlSession:如果查询发生在不同的 SqlSession 中,一级缓存会失效
  • 执行更新操作:当执行了 INSERT、UPDATE 或 DELETE 语句后,MyBatis 会默认清空当前 SqlSession 的一级缓存
  • 显式清空缓存:如果在代码中调用了 SqlSession 的 clearCache() 方法,会清空一级缓存
  • 查询条件发生变化:如果查询的 SQL 或参数发生变化,一级缓存不会命中

原理探究

一级缓存到底是什么?一级缓存什么时候被创建?一级缓存的工作流程是什么样子的?

通过源码分析,可以发现:

  1. 在 BaseExecutor 中可以查看缓存相关的代码实现
  2. clearCache 方法和缓存相关
  3. Perpetualcache 本质上就是一个大的 Map<Object, Object>
  4. 创建缓存时会经过一系列的 UPDATE 方法,由 CacheKey 对象来执行

一级缓存是基于 HashMap 实现的本地缓存,默认开启,生命周期与 SqlSession 一致。