0
点赞
收藏
分享

微信扫一扫

Java-11 深入浅出 MyBatis - 一级缓存 代码测试 与 原理探究 源码 Executor


点一下关注吧!!!非常感谢!!持续更新!!!

大数据篇正在更新!

  • MyBatis(正在更新)

一级缓存

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

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

编写代码

一个 SqlSession中,对 User 表根据 ID 进行两次查询,查看 SQL 语句的情况。

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
[WzkUser(id=1, username=wzk, pass..

对应的截图如下所示:

Java-11 深入浅出 MyBatis - 一级缓存 代码测试 与 原理探究 源码 Executor_mybatis


我们可以观察到,两次查询中间是没有执行的 SQL,这就是 MyBatis 的缓存机制。

编写代码

我这里为了测试方便,编写了一个 UPDATE 的方法,这里就不放代码了,大家自行实现就好。
这次我们在两次查询中间,做了一个 UPDATE 的操作。

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();
    }

}

运行结果

运行上述的代码,控制台的输出内容如下所示:

24/11/13 10:19:15 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:19:15 DEBUG UserMapper.findAll: ==> Parameters: 
24/11/13 10:19:15 DEBUG UserMapper.findAll: <==      Total: 3
[WzkUser(id=1, usern

对应的截图如下所示:

Java-11 深入浅出 MyBatis - 一级缓存 代码测试 与 原理探究 源码 Executor_mybatis_02


可以观察到,这次的运行结果,是:SQL 查询、更新操作、SQL 查询,和之前不同,这就是因为 UPDATE 之后,MyBatis 会清空缓存。

暂时小结

第一次发起查询的时候,先去缓存中查询是否命中,如果没有,从数据库中查询数据出来,将对应的数据保存到一级缓存中。
如果中间做了 SqlSession 的 Commit 操作,此时则会清空 SqlSession 的一级缓存,为了中缓存中拿到最新的消息,避免脏读。
第二次发起查询用户信息的时候,先去缓存中查询是否有对应的缓存,如果有直接取。

Java-11 深入浅出 MyBatis - 一级缓存 代码测试 与 原理探究 源码 Executor_springboot_03

工作原理

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

工作流程

查询前检查缓存

SqlSession 会先检查缓存中是否已经存在相应的数据(即是否有相同 SQL 和参数的查询结果)。
如果缓存命中,则直接返回缓存中的结果,不再执行 SQL 语句。

查询后更新缓存

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

缓存的失效

在某些情况下,一级缓存会失效(见下文)。

一级缓存的特点

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

一级缓存的生效条件

为了确保缓存的正确性,一级缓存有一定的生效条件,只有在以下情况下缓存才会命中:

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

一级缓存的失效场景

一级缓存会在以下情况下失效:

  • 不同的 SqlSession:如果查询发生在不同的 SqlSession 中,一级缓存会失效,因为每个 SqlSession 都有独立的缓存。
  • 执行更新操作:当执行了 INSERT、UPDATE 或 DELETE 语句后,MyBatis 会默认清空当前 SqlSession 的一级缓存,以保证数据的一致性。
  • 显式清空缓存:如果在代码中调用了 SqlSession 的 clearCache() 方法,会清空一级缓存。
  • 查询条件发生变化:如果查询的 SQL 或参数发生变化(即使是查询相同的表),一级缓存不会命中。

原理探究

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

我们可以一级一级的跟进 SqlSession 中进行查看。
从前面我们手动实现 MyBatis 的过程中可以看出来,比如 SqlSession是个接口,那肯定有一个对应的实现类(大概率会叫:DefaultSqlSession),比如 Executor,那肯定也会有一个对应的实现类(大概率叫 DefaultExecutor),我们一级一级跟入代码。

我们将会在:BaseExecutor.java 中查看到如下的代码:

Java-11 深入浅出 MyBatis - 一级缓存 代码测试 与 原理探究 源码 Executor_缓存_04

(略过整个查看源码的过程,可以自行查看,大概有个概念即可)。
在上述的所有方法中,clearCache 是和缓存相关的,我们要对上下级关系有一个清晰的了解。

Java-11 深入浅出 MyBatis - 一级缓存 代码测试 与 原理探究 源码 Executor_mysql_05

再深入分析,流程走到 Perpetualcache 中的 clear 方法之后,会调用其他的 clear 方法,会看到一个 cache,这个 cache 是什么呢,本质上就是 new HashMap()。
也就是说,cache.clear() 实际上是 map.clear(),也就是说 SqlSession 中是有一个 HashMap 的。

Executor

我们跟入到 Executor 中,看几个方法,比如 commit 的时候,会调用 clearLocalCache 的方法。

Java-11 深入浅出 MyBatis - 一级缓存 代码测试 与 原理探究 源码 Executor_缓存_06


在上述定义的地方,有对 localCache 的定义:

Java-11 深入浅出 MyBatis - 一级缓存 代码测试 与 原理探究 源码 Executor_缓存_07


而 PerpetualCache 本质上就是一个大的 Map<Object, Object>

Java-11 深入浅出 MyBatis - 一级缓存 代码测试 与 原理探究 源码 Executor_mysql_08


而创建缓存的地方在这里:

Java-11 深入浅出 MyBatis - 一级缓存 代码测试 与 原理探究 源码 Executor_mysql_09


创建 Key 会经过一系列的 UPDATE 方法,UPDATE 方法由一个 CacheKey 对象来执行,这个 UPDATE 方法最终由 updateList 把这 5 个值都存起来。创建缓存结束之后,我们会在 query 的时候用到:

Java-11 深入浅出 MyBatis - 一级缓存 代码测试 与 原理探究 源码 Executor_springboot_10


举报

相关推荐

0 条评论