文章目录
- 第9章:MyBatis多级缓存和懒加载
- 一级缓存
- 二级缓存怎么使用
- 懒加载
第9章:MyBatis多级缓存和懒加载
一级缓存
什么是缓存?
一级缓存核心定位
- 一级缓存是 MyBatis 内置的默认缓存机制,无需手动配置,默认开启。
- 作用域:
- 仅限当前
SqlSession(数据库会话) - 不同 SqlSession 之间的缓存相互隔离。
- 仅限当前
- 缓存介质:
- 内存(基于
HashMap存储),缓存键由Mapper ID + SQL 语句 + 参数 + 分页信息 + 环境信息组成。
核心价值:
减少同一 SqlSession 内重复查询的数据库交互,提升查询性能。
一级缓存生效与失效条件
场景类型 生效条件 失效条件 核心前提 同一 SqlSession、相同 Mapper ID+SQL + 参数 不同 SqlSession、SQL / 参数 / 分页不同 数据库操作影响 未执行增删改(insert/update/delete)操作 执行增删改操作(自动清空当前 SqlSession 缓存) 手动干预 未调用 clearCache()或close()方法调用 sqlSession.clearCache()(手动清空)、sqlSession.close()(关闭会话)配置影响 默认配置(无特殊禁用) 全局配置 localCacheScope=STATEMENT(禁用一级缓存)
一级缓存工作流程
一级缓存核心特性:
@Service@Slf4jpublicclassUserService{@AutowiredprivateSqlSessionTemplatesqlSessionTemplate;/*一级缓存 有效的访问*/publicvoidfirstCache(){SqlSessionFactorysqlSessionFactory=sqlSessionTemplate.getSqlSessionFactory();SqlSessionsqlSession=sqlSessionFactory.openSession();UserMapperuserMapper=sqlSession.getMapper(UserMapper.class);Useruser=userMapper.selectById(1);log.info("第一次查询结果:{}",user);Useruser2=userMapper.selectById(1);log.info("第二次查询结果:{}",user2);log.info("两个user是否是同一个对象:{}",user==user2);sqlSession.close();}/*一级缓存 失效的访问*/publicvoidfirstCacheInvalidation(){SqlSessionFactorysqlSessionFactory=sqlSessionTemplate.getSqlSessionFactory();SqlSessionsqlSession=sqlSessionFactory.openSession();UserMapperuserMapper=sqlSession.getMapper(UserMapper.class);Useruser=userMapper.selectById(1);log.info("第一次查询结果:{}",user);/*执行 写的操作*/user.setAge(18);introws=userMapper.update(user);log.info("执行写的操作,影响行数:{}",rows);//清空缓存// sqlSession.clearCache();Useruser2=userMapper.selectById(1);log.info("第二次查询结果:{}",user2);log.info("两个user是否是同一个对象:{}",user==user2);sqlSession.close();}}二级缓存怎么使用
核心概念界定
二级缓存核心定位
- 二级缓存(Mapper 级缓存)是 MyBatis 的跨 SqlSession 缓存
- 作用域为同一个 Mapper(namespace)
- 不同 SqlSession 可共享缓存数据。
- 缓存介质:
- 默认内存(HashMap),支持自定义(如 Redis等第三方缓存)
- 核心价值:
- 减少不同 SqlSession 间重复查询的数据库交互
- 适用于查询频率高、修改频率低的数据(如字典表、配置表)
- 依赖条件:实体类需实现
Serializable接口,需手动开启
一级缓存与二级缓存核心区别
| 对比维度 | 一级缓存(SqlSession 级) | 二级缓存(Mapper 级) |
|---|---|---|
| 作用域 | 单个 SqlSession | 同一个 Mapper(namespace) |
| 共享性 | 不可跨 SqlSession 共享 | 可跨 SqlSession 共享 |
| 开启方式 | 默认开启,无需配置 | 需全局配置 + Mapper 配置手动开启 |
| 实体类要求 | 无强制序列化要求 | 必须实现 Serializable 接口 |
| 失效触发 | 同一 SqlSession 内增删改、clearCache () | 对应 Mapper 内增删改操作、缓存过期等 |
| 适用场景 | 单会话内重复查询 | 多会话共享高频查询数据 |
二级缓存工作流程
二级缓存开启条件(三步缺一不可)
- 全局配置开启:
- springBoot 配置文件中设置
cacheEnabled=true
- springBoot 配置文件中设置
- Mapper 级开启:
- 在 Mapper XML 中添加
<cache/>标签
- 在 Mapper XML 中添加
- 实体类序列化:
- 缓存的实体类必须实现
java.io.Serializable接口(避免序列化异常)。
- 缓存的实体类必须实现
二级缓存配置详解
mybatis:configuration:cache-enabled:true# 启用二级缓存<!-- Mapper XML 中配置二级缓存 --><mappernamespace="com.example.mapper.UserMapper"><!-- 开启二级缓存配置 --><cacheeviction="LRU"flushInterval="60000"size="512"readOnly="true"/><selectid="selectUserById"parameterType="long"resultType="User"useCache="true">SELECT * FROM user WHERE id = #{id}</select></mapper>二级缓存属性详解表
| 属性 | 可选值 | 默认值 | 说明 |
|---|---|---|---|
eviction | LRU/FIFO/SOFT/WEAK | LRU | 缓存回收策略 |
flushInterval | 毫秒数 | 无 | 缓存刷新间隔 |
size | 正整数 | 1024 | 缓存引用数量 |
readOnly | true/false | false | 是否只读 |
blocking | true/false | false | 是否使用阻塞缓存 |
常见的缓存回收策略:
| 策略缩写 | 全称 | 策略说明 |
|---|---|---|
| LRU | Least Recently Used | 移除最长时间未被使用的对象(默认策略)。 |
| FIFO | First In First Out | 按对象进入缓存的顺序来移除它们。 |
| SOFT | Soft Reference | 基于垃圾回收器状态和软引用规则来移除对象。 |
| WEAK | Weak Reference | 更积极地基于垃圾收集器状态和弱引用规则移除对象。 |
// 使用二级缓存需要实体类实现Serializable接口@Data@NoArgsConstructor@AllArgsConstructorpublicclassUserimplementsSerializable{privateLongid;privateStringusername;privateStringemail;privateIntegerage;privateDatecreateTime;privateDateupdateTime;// 关联对象也需要序列化}// 二级缓存服务演示@Service@Slf4jpublicclassSecondLevelCacheService{@AutowiredprivateUserMapperuserMapper;/** * 演示二级缓存跨SqlSession共享 */publicvoiddemonstrateSecondLevelCache(){Useruser1=userMapper.selectUserById(1L);log.info("SqlSession查询: {}",user1);// 重新调用方法模拟第二个SqlSession(实际应用中可能是另一个请求)log.info("=== 二级缓存演示结束 ===");}/** * 二级缓存失效场景 */publicvoiddemonstrateSecondLevelCacheInvalidation(){// 执行更新操作Useruser1=newUser();user1.setId(1L);user1.setEmail("newemail@example.com");intupdateCount=userMapper.updateUser(user1);log.info("更新影响行数: {}",updateCount);}}懒加载
懒加载核心定位
- 懒加载(延迟加载)是 MyBatis 关联查询的性能优化机制
- 指查询主对象时,不立即加载关联对象
- 而是在首次访问关联对象时才触发查询xa
- 对立概念:
- 立即加载(默认行为),查询主对象时同时加载所有关联对象
- 核心价值:
- 避免不必要的关联查询,
- 减少数据库压力
- 如仅需查询用户基本信息时,无需加载其所有订单
- 适用场景:
- 一对一、一对多、多对多关联查询,且关联数据不总是需要使用
懒加载与立即加载对比
懒加载开启条件
- 全局配置开启延迟加载:
lazyLoadingEnabled=true(默认 false)
- 关闭积极加载:
aggressiveLazyLoading=false- SpringBoot 2.x+ 已默认关闭,确保按需加载
- 关联标签配置(可选):
association或collection标签添加fetchType="lazy"(优先级高于全局配置)
懒加载配置详解
mybatis:configuration:lazy-loading-enabled:true# 开启全局懒加载aggressive-lazy-loading:false# 关闭积极加载(按需加载)map-underscore-to-camel-case:true懒加载实战案例
<!-- 基础用户结果映射(不包含关联对象) --><resultMapid="BaseUserMap"type="com.example.entity.User"><idcolumn="user_id"property="userId"/><resultcolumn="user_name"property="userName"/><resultcolumn="card_id"property="cardId"/></resultMap><!-- 嵌套查询结果映射:使用子查询方式加载关联对象 --><resultMapid="UserWithIdCardNestedQueryMap"type="com.example.entity.User"extends="BaseUserMap"><associationproperty="idCard"column="card_id"select="com.example.mapper.IdCardMapper.selectById"javaType="com.example.entity.IdCard"fetchType="lazy"/></resultMap><!-- 嵌套查询方式:查询用户(触发子查询加载身份证) --><selectid="selectUserWithIdCardNested"resultMap="UserWithIdCardNestedQueryMap">SELECT user_id, user_name, card_id FROM t_user WHERE user_id = #{userId}</select>懒加载最佳实践
适用场景:
- 关联数据量大,且不经常使用
- 需要快速响应的列表查询
- 移动端应用,减少数据传输
注意事项:
) -->
SELECT
user_id,
user_name,
card_id
FROM t_user
WHERE user_id = #{userId}
懒加载最佳实践 - 适用场景: - 关联数据量大,且不经常使用 - 需要快速响应的列表查询 - 移动端应用,减少数据传输 - 注意事项: - 注意 N+1 查询问题