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();
在这个例子中,我们先通过sqlSession
的getMapper
方法获取UserMapper
接口的代理对象,然后调用getUserById(1)
方法查询ID为1的用户。
第一次查询时,由于一级缓存是空的,所以会去数据库里查,然后把查到的数据放到一级缓存里。
第二次查询时,由于一级缓存里已经有了ID为1的用户,所以直接从缓存里拿,不用再去数据库里查了。
最后,我们判断两次查询是否是同一个对象,结果是true
,说明两次查询确实是同一个对象,也就是从缓存里拿的。
2.5 何时会刷新一级缓存?
以下情况会刷新一级缓存:
- 执行了
commit
操作:当SqlSession执行了commit
操作时,会清空一级缓存。 - 执行了
rollback
操作:当SqlSession执行了rollback
操作时,会清空一级缓存。 - 执行了
update
、insert
、delete
操作:当SqlSession执行了update
、insert
、delete
操作时,会清空一级缓存。 这是因为这些操作可能会改变数据库里的数据,为了保证缓存里的数据和数据库里的数据一致,所以需要清空缓存。 - 手动调用
clearCache()
方法:可以手动调用sqlSession.clearCache()
方法来清空一级缓存。
2.6 一级缓存的优点和缺点
优点:
- 使用简单,不需要手动配置。
- 能提高程序的运行速度。
缺点:
- 缓存范围小,只能在同一个SqlSession里共享。
- 容易出现脏数据,例如,如果在同一个SqlSession里先查询了一条数据,然后又修改了这条数据,那么缓存里的数据就和数据库里的数据不一致了。
3. 二级缓存:你的“公共图书馆”
二级缓存,也叫全局缓存,它是namespace级别的缓存。 也就是说,每个namespace都有一个自己的“公共图书馆”,用来记录自己查询过的数据。
3.1 二级缓存的原理
当SqlSession执行查询操作时,MyBatis会先去二级缓存里找。 如果找到了,就直接返回缓存里的数据; 如果没找到,就去一级缓存里找; 如果一级缓存里找到了,就直接返回缓存里的数据; 如果一级缓存里也没找到,就去数据库里查,然后把查到的数据放到一级缓存和二级缓存里,下次再查同样的数据,就能直接从缓存里拿了。
3.2 二级缓存的生命周期
二级缓存的生命周期和应用程序的生命周期是一样的。 也就是说,当应用程序启动时,二级缓存也创建了;当应用程序关闭时,二级缓存也就消失了。
3.3 二级缓存的配置
二级缓存默认是关闭的,需要手动配置。 配置方法如下:
-
在MyBatis的配置文件(
mybatis-config.xml
)中,开启二级缓存的总开关:<settings> <setting name="cacheEnabled" value="true"/> </settings>
-
在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
。
-
确保你的实体类实现了
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));
在这个例子中,我们先通过sqlSessionFactory
的openSession
方法创建两个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的缓存机制,并在实际项目中灵活运用。
记住,缓存是一把双刃剑,用好了能让你的程序“飞起来”,用不好就可能变成“坑”。 所以,在使用缓存之前,一定要仔细考虑你的实际情况,选择最适合你的缓存方案。
好了,今天的分享就到这里,咱们下期再见! 祝各位编程愉快!