MyBatis 缓存机制:一级缓存与二级缓存的配置与原理

MyBatis 缓存机制:一级缓存与二级缓存的配置与原理

各位看官,大家好!今天咱们来聊聊MyBatis的缓存机制,这玩意儿就像咱们程序猿的“百宝箱”,用好了能让你的数据库“起飞”,用不好嘛…就可能变成性能的“绊脚石”。 别怕,今天咱们就来扒一扒它的底裤,看看一级缓存和二级缓存到底是怎么一回事儿。

1. 为什么要用缓存?

首先,咱得明白为什么要用缓存。这道理很简单,就像你经常要查字典,如果每次都从头开始翻,那得多累啊!但如果你把经常查的词记在一个小本本上,下次再查就方便多了。

数据库也是一样,每次都去数据库里查数据,那得多耗时啊!所以,MyBatis就提供了缓存机制,把查询结果先存在一个地方,下次再查同样的数据,就直接从缓存里拿,不用再去数据库“折腾”了。 这样一来,不仅能减少数据库的压力,还能大大提高程序的运行速度,简直就是“一箭双雕”!

2. 一级缓存:你的“专属小本本”

一级缓存,也叫本地缓存,它是SqlSession级别的缓存。 也就是说,每个SqlSession都有一个自己专属的“小本本”,用来记录自己查询过的数据。

2.1 一级缓存的原理

当SqlSession执行查询操作时,MyBatis会先去一级缓存里找。 如果找到了,就直接返回缓存里的数据; 如果没找到,就去数据库里查,然后把查到的数据放到一级缓存里,下次再查同样的数据,就能直接从缓存里拿了。

2.2 一级缓存的生命周期

一级缓存的生命周期和SqlSession的生命周期是一样的。 也就是说,当SqlSession创建时,一级缓存也创建了;当SqlSession关闭时,一级缓存也就消失了。

2.3 一级缓存的配置

一级缓存是默认开启的,不需要手动配置。 也就是说,只要你使用了MyBatis,就默认会使用一级缓存。

2.4 一级缓存的示例

咱们来看个例子:

// 假设有一个UserMapper接口,里面有个getUserById方法
User user1 = sqlSession.getMapper(UserMapper.class).getUserById(1);
System.out.println("第一次查询:" + user1);

User user2 = sqlSession.getMapper(UserMapper.class).getUserById(1);
System.out.println("第二次查询:" + user2);

// 判断两次查询是否是同一个对象
System.out.println("两次查询是否是同一个对象:" + (user1 == user2));

// 关闭SqlSession
sqlSession.close();

在这个例子中,我们先通过sqlSessiongetMapper方法获取UserMapper接口的代理对象,然后调用getUserById(1)方法查询ID为1的用户。

第一次查询时,由于一级缓存是空的,所以会去数据库里查,然后把查到的数据放到一级缓存里。

第二次查询时,由于一级缓存里已经有了ID为1的用户,所以直接从缓存里拿,不用再去数据库里查了。

最后,我们判断两次查询是否是同一个对象,结果是true,说明两次查询确实是同一个对象,也就是从缓存里拿的。

2.5 何时会刷新一级缓存?

以下情况会刷新一级缓存:

  • 执行了commit操作:当SqlSession执行了commit操作时,会清空一级缓存。
  • 执行了rollback操作:当SqlSession执行了rollback操作时,会清空一级缓存。
  • 执行了updateinsertdelete操作:当SqlSession执行了updateinsertdelete操作时,会清空一级缓存。 这是因为这些操作可能会改变数据库里的数据,为了保证缓存里的数据和数据库里的数据一致,所以需要清空缓存。
  • 手动调用clearCache()方法:可以手动调用sqlSession.clearCache()方法来清空一级缓存。

2.6 一级缓存的优点和缺点

优点:

  • 使用简单,不需要手动配置。
  • 能提高程序的运行速度。

缺点:

  • 缓存范围小,只能在同一个SqlSession里共享。
  • 容易出现脏数据,例如,如果在同一个SqlSession里先查询了一条数据,然后又修改了这条数据,那么缓存里的数据就和数据库里的数据不一致了。

3. 二级缓存:你的“公共图书馆”

二级缓存,也叫全局缓存,它是namespace级别的缓存。 也就是说,每个namespace都有一个自己的“公共图书馆”,用来记录自己查询过的数据。

3.1 二级缓存的原理

当SqlSession执行查询操作时,MyBatis会先去二级缓存里找。 如果找到了,就直接返回缓存里的数据; 如果没找到,就去一级缓存里找; 如果一级缓存里找到了,就直接返回缓存里的数据; 如果一级缓存里也没找到,就去数据库里查,然后把查到的数据放到一级缓存和二级缓存里,下次再查同样的数据,就能直接从缓存里拿了。

3.2 二级缓存的生命周期

二级缓存的生命周期和应用程序的生命周期是一样的。 也就是说,当应用程序启动时,二级缓存也创建了;当应用程序关闭时,二级缓存也就消失了。

3.3 二级缓存的配置

二级缓存默认是关闭的,需要手动配置。 配置方法如下:

  1. 在MyBatis的配置文件(mybatis-config.xml)中,开启二级缓存的总开关:

    <settings>
      <setting name="cacheEnabled" value="true"/>
    </settings>
  2. 在Mapper的配置文件(例如UserMapper.xml)中,配置二级缓存:

    <cache
      eviction="LRU"
      flushInterval="60000"
      size="512"
      readOnly="true"/>
    • eviction:缓存回收策略,常用的有:
      • LRU(Least Recently Used):最近最少使用,移除最长时间不被使用的对象。
      • FIFO(First In First Out):先进先出,按对象进入缓存的顺序来移除它们。
      • SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象。
      • WEAK:弱引用,更积极地移除基于垃圾回收器状态和弱引用规则的对象。
    • flushInterval:刷新间隔,单位是毫秒。如果设置为0,则不自动刷新。
    • size:缓存对象的最大数量。
    • readOnly:是否只读。如果设置为true,则所有被缓存的对象都是只读的,不能被修改。 这可以提高缓存的性能,但如果需要修改缓存里的数据,就不能设置为true
  3. 确保你的实体类实现了java.io.Serializable接口:

    public class User implements Serializable {
        private static final long serialVersionUID = 1L;
        // ...
    }

    这是因为二级缓存需要把对象序列化到磁盘上,所以实体类必须实现Serializable接口。

3.4 二级缓存的示例

咱们来看个例子:

// 假设有一个UserMapper接口,里面有个getUserById方法
SqlSession sqlSession1 = sqlSessionFactory.openSession();
User user1 = sqlSession1.getMapper(UserMapper.class).getUserById(1);
System.out.println("第一次查询:" + user1);
sqlSession1.close(); // 关闭sqlSession1,数据才会放到二级缓存

SqlSession sqlSession2 = sqlSessionFactory.openSession();
User user2 = sqlSession2.getMapper(UserMapper.class).getUserById(1);
System.out.println("第二次查询:" + user2);
sqlSession2.close();

// 判断两次查询是否是同一个对象
System.out.println("两次查询是否是同一个对象:" + (user1 == user2));

在这个例子中,我们先通过sqlSessionFactoryopenSession方法创建两个sqlSession对象,然后分别调用getUserById(1)方法查询ID为1的用户。

第一次查询时,由于二级缓存是空的,所以会去数据库里查,然后把查到的数据放到一级缓存和二级缓存里。 注意,只有当sqlSession1关闭时,一级缓存里的数据才会放到二级缓存里。

第二次查询时,由于二级缓存里已经有了ID为1的用户,所以直接从缓存里拿,不用再去数据库里查了。

最后,我们判断两次查询是否是同一个对象,结果是false,说明两次查询不是同一个对象,而是两个不同的对象。 这是因为二级缓存里存的是对象的副本,而不是对象本身。

3.5 如何刷新二级缓存?

以下情况会刷新二级缓存:

  • 当Mapper配置文件中有flushCache="true"的SQL语句被执行时,会清空二级缓存。 例如:

    <update id="updateUser" parameterType="User" flushCache="true">
        update user set name = #{name} where id = #{id}
    </update>

    在这个例子中,当执行updateUser语句时,会清空二级缓存。

  • 手动调用clearCache()方法:可以手动调用sqlSessionFactory.getConfiguration().getCache(namespace).clear()方法来清空二级缓存。 其中,namespace是Mapper的namespace。

3.6 二级缓存的优点和缺点

优点:

  • 缓存范围大,可以在多个SqlSession里共享。
  • 能提高程序的运行速度。

缺点:

  • 配置复杂,需要手动配置。
  • 容易出现脏数据,例如,如果在不同的SqlSession里先查询了一条数据,然后又修改了这条数据,那么缓存里的数据就和数据库里的数据不一致了。
  • 需要实现序列化接口,增加了代码的复杂度。

4. 一级缓存和二级缓存的区别

特性 一级缓存 (Local Cache) 二级缓存 (Global Cache)
作用范围 SqlSession 级别 Namespace 级别
生存周期 SqlSession 的生命周期 Application 的生命周期
配置 默认开启 需要手动配置
数据共享 同一个 SqlSession 多个 SqlSession (同一个 Namespace)
数据一致性 较低,容易出现脏数据 较低,容易出现脏数据
序列化要求 需要实现 Serializable 接口

5. 缓存的选择:鱼和熊掌兼得?

那么问题来了,一级缓存和二级缓存,我们该选哪个呢?

其实,这就像鱼和熊掌,不能兼得。 但我们可以根据实际情况来选择:

  • 如果你的应用程序对数据一致性要求很高,那么最好不要使用缓存,或者只使用一级缓存。
  • 如果你的应用程序对性能要求很高,而且对数据一致性要求不高,那么可以使用二级缓存。
  • 如果你的应用程序既对性能有要求,又对数据一致性有要求,那么可以考虑使用一些其他的缓存方案,例如Redis、Memcached等。

6. 总结

今天,咱们一起学习了MyBatis的缓存机制,包括一级缓存和二级缓存的原理、配置、优缺点以及如何选择。 希望这篇文章能帮助你更好地理解MyBatis的缓存机制,并在实际项目中灵活运用。

记住,缓存是一把双刃剑,用好了能让你的程序“飞起来”,用不好就可能变成“坑”。 所以,在使用缓存之前,一定要仔细考虑你的实际情况,选择最适合你的缓存方案。

好了,今天的分享就到这里,咱们下期再见! 祝各位编程愉快!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注