第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(禁用一级缓存)

一级缓存工作流程

一级缓存核心特性:

java 复制代码
@Service
@Slf4j
public class UserService {

    @Autowired
    private SqlSessionTemplate sqlSessionTemplate;

    /*一级缓存 有效的访问*/
    public void firstCache(){

        SqlSessionFactory sqlSessionFactory = sqlSessionTemplate.getSqlSessionFactory();
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        User user = userMapper.selectById(1);
        log.info("第一次查询结果:{}",user);

        User user2 = userMapper.selectById(1);
        log.info("第二次查询结果:{}",user2);

        log.info("两个user是否是同一个对象:{}",user==user2);

        sqlSession.close();
    }

    /*一级缓存 失效的访问*/
    public void firstCacheInvalidation(){
        SqlSessionFactory sqlSessionFactory = sqlSessionTemplate.getSqlSessionFactory();
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        User user = userMapper.selectById(1);
        log.info("第一次查询结果:{}",user);

        /*执行 写的操作*/
        user.setAge(18);
        int rows = userMapper.update(user);
        log.info("执行写的操作,影响行数:{}",rows);
        //清空缓存
//        sqlSession.clearCache();

        User user2 = 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
  • Mapper 级开启:
    • 在 Mapper XML 中添加 <cache/> 标签
  • 实体类序列化:
    • 缓存的实体类必须实现 java.io.Serializable 接口(避免序列化异常)。

二级缓存配置详解

yaml 复制代码
mybatis:
  configuration:
    cache-enabled: true  # 启用二级缓存
xml 复制代码
<!-- Mapper XML 中配置二级缓存 -->
<mapper namespace="com.example.mapper.UserMapper">
    
    <!-- 开启二级缓存配置 -->
    <cache
        eviction="LRU"
        flushInterval="60000"
        size="512"
        readOnly="true"/>
    
    <select id="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 更积极地基于垃圾收集器状态和弱引用规则移除对象。
java 复制代码
// 使用二级缓存需要实体类实现Serializable接口
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
    private Long id;
    private String username;
    private String email;
    private Integer age;
    private Date createTime;
    private Date updateTime;
    // 关联对象也需要序列化
}
java 复制代码
// 二级缓存服务演示
@Service
@Slf4j
public class SecondLevelCacheService {
    
    @Autowired
    private UserMapper userMapper;
    
    /**
     * 演示二级缓存跨SqlSession共享
     */
    public void demonstrateSecondLevelCache() {
        User user1 = userMapper.selectUserById(1L);
        log.info("SqlSession查询: {}", user1);
        // 重新调用方法模拟第二个SqlSession(实际应用中可能是另一个请求)
        log.info("=== 二级缓存演示结束 ===");
    }
    
    /**
     * 二级缓存失效场景
     */
    public void demonstrateSecondLevelCacheInvalidation() { 
        // 执行更新操作
        User user1 = new User();
        user1.setId(1L);
        user1.setEmail("newemail@example.com");
        int updateCount = userMapper.updateUser(user1);
        log.info("更新影响行数: {}", updateCount);
    }
}

懒加载

懒加载核心定位

  • 懒加载(延迟加载)是 MyBatis 关联查询的 性能优化机制
    • 指查询主对象时,不立即加载关联对象
    • 而是在首次访问关联对象时才触发查询xa
  • 对立概念:
    • 立即加载(默认行为),查询主对象时同时加载所有关联对象
  • 核心价值:
    • 避免不必要的关联查询,
    • 减少数据库压力
    • 如仅需查询用户基本信息时,无需加载其所有订单
  • 适用场景:
    • 一对一、一对多、多对多关联查询,且关联数据不总是需要使用

懒加载与立即加载对比

懒加载开启条件

  • 全局配置开启延迟加载:
    • lazyLoadingEnabled=true(默认 false)
  • 关闭积极加载:
    • aggressiveLazyLoading=false
    • SpringBoot 2.x+ 已默认关闭,确保按需加载
  • 关联标签配置(可选):
    • associationcollection 标签添加 fetchType="lazy"(优先级高于全局配置)

懒加载配置详解

yaml 复制代码
mybatis:
  configuration:
    lazy-loading-enabled: true # 开启全局懒加载
    aggressive-lazy-loading: false # 关闭积极加载(按需加载)
    map-underscore-to-camel-case: true

懒加载实战案例

xml 复制代码
    <!-- 基础用户结果映射(不包含关联对象) -->
    <resultMap id="BaseUserMap" type="com.example.entity.User">
        <id column="user_id" property="userId"/>
        <result column="user_name" property="userName"/>
        <result column="card_id" property="cardId"/>
    </resultMap>
    
    <!-- 嵌套查询结果映射:使用子查询方式加载关联对象 -->
    <resultMap id="UserWithIdCardNestedQueryMap" type="com.example.entity.User" extends="BaseUserMap">
        <association property="idCard" 
                     column="card_id"
                     select="com.example.mapper.IdCardMapper.selectById"         
                     javaType="com.example.entity.IdCard"
                     
                     fetchType="lazy"
                     
                     />
    </resultMap>
    
    <!-- 嵌套查询方式:查询用户(触发子查询加载身份证) -->
    <select id="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 查询问题
相关推荐
JH30735 小时前
SpringBoot 优雅处理金额格式化:拦截器+自定义注解方案
java·spring boot·spring
qq_12498707538 小时前
基于SSM的动物保护系统的设计与实现(源码+论文+部署+安装)
java·数据库·spring boot·毕业设计·ssm·计算机毕业设计
Coder_Boy_8 小时前
基于SpringAI的在线考试系统-考试系统开发流程案例
java·数据库·人工智能·spring boot·后端
2301_818732068 小时前
前端调用控制层接口,进不去,报错415,类型不匹配
java·spring boot·spring·tomcat·intellij-idea
汤姆yu12 小时前
基于springboot的尿毒症健康管理系统
java·spring boot·后端
暮色妖娆丶12 小时前
Spring 源码分析 单例 Bean 的创建过程
spring boot·后端·spring
biyezuopinvip13 小时前
基于Spring Boot的企业网盘的设计与实现(任务书)
java·spring boot·后端·vue·ssm·任务书·企业网盘的设计与实现
JavaGuide13 小时前
一款悄然崛起的国产规则引擎,让业务编排效率提升 10 倍!
java·spring boot
figo10tf14 小时前
Spring Boot项目集成Redisson 原始依赖与 Spring Boot Starter 的流程
java·spring boot·后端
zhangyi_viva14 小时前
Spring Boot(七):Swagger 接口文档
java·spring boot·后端