引言
在现代Web应用中,数据库访问往往是性能瓶颈之一。MyBatis作为流行的持久层框架,其缓存机制是提升应用性能的关键特性。理解MyBatis的一二级缓存不仅有助于优化应用性能,还能避免因缓存不当导致的数据一致性问题。本文将从基础概念到高级原理,全方位解析MyBatis缓存机制。
一、缓存的基本概念:为什么需要缓存?
1.1 缓存的价值
想象一下,如果你每次需要知道时间都去天文台查询,效率会很低。相反,看一眼手表(缓存)就能立即获取时间。MyBatis缓存扮演的就是这个“手表”的角色,它避免了频繁访问数据库(天文台),极大提升了查询效率。
1.2 缓存的经济学原理
- 时间局部性:刚被访问的数据很可能再次被访问
- 空间局部性:相邻的数据很可能被一起访问
- 访问成本:内存访问(纳秒级)vs 磁盘/网络访问(毫秒级)
二、一级缓存:SqlSession级别的缓存
2.1 什么是SqlSession?
在深入一级缓存前,需要先理解SqlSession。SqlSession不是数据库连接(Connection),而是一次数据库对话的抽象:- // SqlSession相当于一次完整对话,不是一通电话
- SqlSession session = sqlSessionFactory.openSession();
- try {
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> // 对话中的多次查询
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> userMapper.getUser(1);<cache size="1024"/><cache flushInterval="1800000"/> // 第一次查询
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> orderMapper.getOrders(1);<cache size="1024"/><cache flushInterval="1800000"/> // 第二次查询
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> accountMapper.getBalance(1);<cache size="1024"/><cache flushInterval="1800000"/> // 第三次查询
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> session.commit();<cache size="1024"/><cache flushInterval="1800000"/> // 确认对话内容
- } finally {
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> session.close();<cache size="1024"/><cache flushInterval="1800000"/> // 结束对话
- }
复制代码 2.2 一级缓存的核心特性
作用范围:SqlSession内部(一次对话)
默认状态:自动开启,无法关闭
生命周期:随SqlSession创建而创建,随其关闭而销毁
2.3 一级缓存的工作原理
- // 示例代码展示一级缓存行为
- public void demonstrateLevel1Cache() {
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> SqlSession session = sqlSessionFactory.openSession();
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> UserMapper mapper = session.getMapper(UserMapper.class);
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/>
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> System.out.println("第一次查询用户1:");
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> User user1 = mapper.selectById(1);<cache size="1024"/><cache flushInterval="1800000"/> // 发SQL:SELECT * FROM user WHERE id=1
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/>
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> System.out.println("第二次查询用户1:");
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> User user2 = mapper.selectById(1);<cache size="1024"/><cache flushInterval="1800000"/> // 不发SQL!从一级缓存读取
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/>
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> System.out.println("查询用户2:");
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> User user3 = mapper.selectById(2);<cache size="1024"/><cache flushInterval="1800000"/> // 发SQL:参数不同,缓存未命中
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/>
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> System.out.println("修改用户1:");
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> mapper.updateUser(user1);<cache size="1024"/><cache flushInterval="1800000"/> // 清空一级缓存
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/>
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> System.out.println("再次查询用户1:");
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> User user4 = mapper.selectById(1);<cache size="1024"/><cache flushInterval="1800000"/> // 发SQL:缓存被清空
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/>
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> session.close();
- }
复制代码 2.4 一级缓存的数据结构
一级缓存的实现非常简单直接:- // 一级缓存的核心实现类
- public class PerpetualCache implements Cache {
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> // 核心:就是一个ConcurrentHashMap!
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> private final Map<Object, Object> cache = new ConcurrentHashMap<>();
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/>
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> @Override
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> public void putObject(Object key, Object value) {
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> cache.put(key, value);<cache size="1024"/><cache flushInterval="1800000"/> // 简单的Map.put()
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> }
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/>
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> @Override
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> public Object getObject(Object key) {
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> return cache.get(key);<cache size="1024"/><cache flushInterval="1800000"/> // 简单的Map.get()
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> }
- }
复制代码 缓存Key的生成规则:- // CacheKey包含以下要素,决定两个查询是否"相同"
- // 1. Mapper Id(namespace + method)
- // 2. 分页参数(offset, limit)
- // 3. SQL语句
- // 4. 参数值
- // 5. 环境Id
- // 这意味着:即使SQL相同,参数不同,也会生成不同的CacheKey
复制代码 2.5 一级缓存的失效场景
- 执行任何UPDATE/INSERT/DELETE操作
- 手动调用clearCache()
- 设置flushCache="true"
- SqlSession关闭
- 查询参数变化(因为CacheKey不同)
三、二级缓存:Mapper级别的全局缓存
3.1 二级缓存的核心特性
作用范围:Mapper级别(跨SqlSession共享)
默认状态:默认关闭,需要手动开启
生命周期:随应用运行而存在
3.2 二级缓存的配置
- <settings>
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <setting name="cacheEnabled" value="true"/>
- </settings>
- <mapper namespace="com.example.UserMapper">
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/>
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache/>
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/>
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/>
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> eviction="LRU"<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/>
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> flushInterval="60000"<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/>
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> size="1024"<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/>
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> readOnly="true"<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/>
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> blocking="false"/><cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/>
- </mapper>
- <select id="selectById" resultType="User" useCache="true">
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> SELECT * FROM user WHERE id = #{id}
- </select>
- <update id="updateUser" flushCache="true">
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> UPDATE user SET name = #{name} WHERE id = #{id}
- </update>
复制代码 3.3 二级缓存的数据结构
二级缓存不像一级缓存那么简单,它采用了装饰器模式:- 二级缓存装饰器链(层层包装):
- ┌─────────────────────────┐
- │<cache size="1024"/><cache flushInterval="1800000"/> SerializedCache<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> │ ← 序列化存储
- │<cache size="1024"/><cache flushInterval="1800000"/> LoggingCache<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> │ ← 日志统计
- │<cache size="1024"/><cache flushInterval="1800000"/> SynchronizedCache<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> │ ← 线程安全
- │<cache size="1024"/><cache flushInterval="1800000"/> LruCache<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> │ ← LRU淘汰
- │<cache size="1024"/><cache flushInterval="1800000"/> PerpetualCache<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> │ ← 基础HashMap
- └─────────────────────────┘
复制代码 每个装饰器都有特定功能:
- PerpetualCache:基础存储,使用HashMap
- LruCache:最近最少使用淘汰
- SynchronizedCache:保证线程安全
- LoggingCache:记录命中率
- SerializedCache:序列化对象,防止修改
3.4 二级缓存的工作流程
- public void demonstrateLevel2Cache() {
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> // 用户A查询(第一个访问者)
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> SqlSession sessionA = sqlSessionFactory.openSession();
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> UserMapper mapperA = sessionA.getMapper(UserMapper.class);
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> User user1 = mapperA.selectById(1);<cache size="1024"/><cache flushInterval="1800000"/> // 查询数据库
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> sessionA.close();<cache size="1024"/><cache flushInterval="1800000"/> // 关键:关闭时才会写入二级缓存
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/>
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> // 用户B查询(不同SqlSession)
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> SqlSession sessionB = sqlSessionFactory.openSession();
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> UserMapper mapperB = sessionB.getMapper(UserMapper.class);
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> User user2 = mapperB.selectById(1);<cache size="1024"/><cache flushInterval="1800000"/> // 从二级缓存读取,不发SQL
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/>
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> // 管理员更新数据
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> SqlSession sessionC = sqlSessionFactory.openSession();
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> UserMapper mapperC = sessionC.getMapper(UserMapper.class);
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> mapperC.updateUser(user1);<cache size="1024"/><cache flushInterval="1800000"/> // 清空相关二级缓存
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> sessionC.commit();
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> sessionC.close();
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/>
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> // 用户D再次查询
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> SqlSession sessionD = sqlSessionFactory.openSession();
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> UserMapper mapperD = sessionD.getMapper(UserMapper.class);
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> User user3 = mapperD.selectById(1);<cache size="1024"/><cache flushInterval="1800000"/> // 缓存被清,重新查询数据库
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> sessionD.close();
- }
复制代码 3.5 二级缓存的同步机制
二级缓存有一个重要特性:事务提交后才更新。这意味着:- // 场景:事务内查询,事务提交前其他会话看不到更新
- SqlSession session1 = sqlSessionFactory.openSession();
- UserMapper mapper1 = session1.getMapper(UserMapper.class);
- // 修改数据,但未提交
- mapper1.updateUser(user);
- // 此时二级缓存还未更新
- // 另一个会话查询
- SqlSession session2 = sqlSessionFactory.openSession();
- UserMapper mapper2 = session2.getMapper(UserMapper.class);
- User user2 = mapper2.selectById(1);<cache size="1024"/><cache flushInterval="1800000"/> // 可能读到旧数据!
- session1.commit();<cache size="1024"/><cache flushInterval="1800000"/> // 提交后,二级缓存才会更新
- // 之后的新查询才会看到新数据
复制代码 四、一二级缓存的对比与选择
4.1 核心差异对比
特性一级缓存二级缓存作用范围SqlSession内部Mapper级别,跨SqlSession默认状态开启关闭数据结构简单HashMap装饰器链共享性私有,不共享公共,所有会话共享生命周期随SqlSession创建销毁随应用运行持久存在性能影响极小(内存访问)中等(可能有序列化开销)适用场景会话内重复查询跨会话共享查询4.2 生活化比喻
一级缓存 = 私人对话记忆
- 你和朋友的聊天内容,只有你们两人知道
- 聊天结束(SqlSession关闭),记忆逐渐模糊
二级缓存 = 公司公告栏
- 重要通知写在公告栏,所有员工都能看到
- 通知更新时,需要擦掉旧的,写上新的
- 公告栏内容持久存在,直到被更新
4.3 使用场景建议
适合一级缓存的场景:
- // 场景1:方法内多次查询相同数据
- public void processOrder(Long orderId) {
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> Order order1 = validateOrder(orderId);<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> // 第一次查数据库
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> Order order2 = calculateDiscount(orderId);<cache size="1024"/><cache flushInterval="1800000"/> // 走一级缓存
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> Order order3 = generateInvoice(orderId);<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> // 走一级缓存
- }
- // 场景2:循环内查询
- for (int i = 0; i < 100; i++) {
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> Config config = configMapper.getConfig("system_timeout");
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> // 只有第一次查数据库,后续99次走缓存
- }
复制代码 适合二级缓存的场景:
- // 场景1:读多写少的配置数据
- SystemConfig config = configMapper.getConfig("app_settings");
- // 多个用户频繁读取,很少修改
- // 场景2:热门商品信息
- Product product = productMapper.getHotProduct(666);
- // 商品详情页,大量用户访问同一商品
- // 场景3:静态字典数据
- List<City> cities = addressMapper.getAllCities();
- // 城市列表,很少变化
复制代码 不适合缓存的场景:
- // 场景1:实时性要求高的数据
- Stock stock = stockMapper.getRealTimeStock(productId);
- // 库存信息,需要实时准确
- // 场景2:频繁更新的数据
- UserBalance balance = accountMapper.getBalance(userId);
- // 用户余额,每次交易都变化
- // 场景3:大数据量查询
- List<Log> logs = logMapper.getTodayLogs();
- // 数据量大,缓存占用内存过多
复制代码 五、缓存的高级特性与原理
5.1 缓存淘汰策略
MyBatis提供了多种淘汰策略:- [/code]可用策略:
- [list]
- [*][b]LRU[/b](Least Recently Used):最近最少使用(默认)
- [*][b]FIFO[/b](First In First Out):先进先出
- [*][b]SOFT[/b]:软引用,内存不足时被GC回收
- [*][b]WEAK[/b]:弱引用,GC时立即回收
- [/list][size=4]5.2 LRU缓存的实现原理[/size]
- [code]public class LruCache implements Cache {
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> private final Cache delegate;
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> // 使用LinkedHashMap实现LRU
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> private Map<Object, Object> keyMap;
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> private Object eldestKey;
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/>
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> public void setSize(final int size) {
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> @Override
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> boolean tooBig = size() > size;
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> if (tooBig) {
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> eldestKey = eldest.getKey();
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> }
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> return tooBig;
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> }
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> };
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> }
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/>
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> @Override
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> public Object getObject(Object key) {
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> // 访问时更新顺序
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> keyMap.get(key);
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> return delegate.getObject(key);
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> }
- }
复制代码 5.3 缓存查询的完整流程
- 查询执行流程:
- 1. 请求到达CachingExecutor(二级缓存入口)
- 2. 生成CacheKey(包含SQL、参数等信息)
- 3. 查询二级缓存
- <cache size="1024"/><cache flushInterval="1800000"/> └─ 命中 → 返回结果
- <cache size="1024"/><cache flushInterval="1800000"/> └─ 未命中 → 继续
- 4. 查询一级缓存
- <cache size="1024"/><cache flushInterval="1800000"/> └─ 命中 → 返回结果,并放入二级缓存(事务提交时)
- <cache size="1024"/><cache flushInterval="1800000"/> └─ 未命中 → 继续
- 5. 查询数据库
- 6. 结果存入一级缓存
- 7. 事务提交时,一级缓存刷入二级缓存
- 8. 返回结果
复制代码 六、缓存的最佳实践与避坑指南
6.1 最佳实践
1. 合理配置缓存大小
- <cache size="1024"/><cache flushInterval="1800000"/>
复制代码 2. 设置合理的刷新间隔
- <cache size="1024"/><cache flushInterval="1800000"/>
复制代码 3. 选择性使用缓存
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> SELECT * FROM realtime_table<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> SELECT * FROM important_table
复制代码 4. 关联查询的缓存策略
- <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/>
复制代码 6.2 常见问题与解决方案
问题1:脏读问题
场景:一个会话修改数据但未提交,另一个会话从二级缓存读取到旧数据。
解决方案:- // 设置事务隔离级别@Transactional(isolation = Isolation.READ_COMMITTED)public void updateUser(User user) {<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> userMapper.updateUser(user);}// 或者在Mapper中设置flushCache@Update("UPDATE user SET name=#{name} WHERE id=#{id}")@Options(flushCache = Options.FlushCachePolicy.TRUE)int updateUser(User user);
复制代码 问题2:内存溢出
场景:缓存大量数据导致JVM内存不足。
解决方案:
- 设置合理的缓存大小和淘汰策略
- 使用软引用/弱引用缓存
- 定期清理不活跃的缓存
问题3:分布式环境缓存不一致
场景:多台服务器,每台有自己的缓存,数据不一致。
解决方案:
- 使用集中式缓存(Redis、Memcached)替代默认二级缓存
- 实现自定义Cache接口:
- public class RedisCache implements Cache {<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> private JedisPool jedisPool;<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> @Override<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> public void putObject(Object key, Object value) {<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> try (Jedis jedis = jedisPool.getResource()) {<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> jedis.set(serialize(key), serialize(value));<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> }<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> }<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> @Override<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> public Object getObject(Object key) {<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> try (Jedis jedis = jedisPool.getResource()) {<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> byte[] value = jedis.get(serialize(key));<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> return deserialize(value);<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> }<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> }}
复制代码 问题4:缓存穿透
场景:查询不存在的数据,每次都查数据库。
解决方案:- // 缓存空对象public User getUser(Long id) {<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> User user = userMapper.selectById(id);<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> if (user == null) {<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> // 缓存空值,设置短过期时间<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> cacheNullValue(id);<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> return null;<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> }<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> return user;}
复制代码 6.3 监控与调试
开启缓存日志
- # 查看缓存命中情况
- logging.level.org.mybatis=DEBUG
- logging.level.com.example.mapper=TRACE
复制代码 监控缓存命中率
- // 获取缓存统计信息Cache cache = sqlSession.getConfiguration()<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> .getCache("com.example.UserMapper");if (cache instanceof LoggingCache) {<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> LoggingCache loggingCache = (LoggingCache) cache;<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> System.out.println("命中次数: " + loggingCache.getHitCount());<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> System.out.println("未命中次数: " + loggingCache.getMissCount());<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> System.out.println("命中率: " +<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> (loggingCache.getHitCount() * 100.0 /<cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> <cache size="1024"/><cache flushInterval="1800000"/> (loggingCache.getHitCount() + loggingCache.getMissCount())) + "%");}
复制代码 七、总结与思考
7.1 核心要点回顾
- 一级缓存:SqlSession级别,自动开启,基于HashMap,简单高效
- 二级缓存:Mapper级别,需手动开启,基于装饰器模式,功能丰富
- 缓存Key:由SQL、参数等要素生成,决定查询是否"相同"
- 事务同步:二级缓存在事务提交后才更新,避免脏读
- 适用场景:根据数据特点选择合适的缓存策略
7.2 设计思想启示
MyBatis缓存设计体现了几个重要软件设计原则:
- 单一职责原则:每个缓存装饰器只负责一个功能
- 开闭原则:通过装饰器模式,无需修改原有代码即可扩展功能
- 接口隔离:Cache接口定义清晰,便于自定义实现
7.3 实际应用建议
在实际项目中:
- 从小开始:先使用一级缓存,确有需要再开启二级缓存
- 测试验证:上线前充分测试缓存效果和内存占用
- 监控调整:生产环境监控缓存命中率,根据实际情况调整配置
- 文档记录:记录缓存配置和策略,便于团队协作和维护
7.4 未来展望
随着微服务和云原生架构的普及,MyBatis缓存也在演进:
- 分布式缓存集成:更好支持Redis等分布式缓存
- 多级缓存策略:本地缓存+分布式缓存的组合使用
- 智能缓存管理:基于访问模式的自动缓存优化
结语
MyBatis缓存机制是一个看似简单实则精妙的设计。理解它不仅能帮助我们优化应用性能,还能加深对缓存设计模式的理解。记住,缓存是提升性能的利器,但也可能成为数据一致的陷阱。合理使用、谨慎配置、持续监控,才能让缓存真正为应用赋能。
缓存不是银弹,而是需要精心调校的利器。 在实际开发中,应根据业务特点、数据特性和访问模式,选择最合适的缓存策略,在性能与一致性之间找到最佳平衡点。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |