第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 查询问题
相关推荐
小马爱打代码15 小时前
SpringBoot:封装 starter
java·spring boot·后端
STARSpace888815 小时前
SpringBoot 整合个推推送
java·spring boot·后端·消息推送·个推
码界奇点17 小时前
基于SpringBoot+Vue的前后端分离外卖点单系统设计与实现
vue.js·spring boot·后端·spring·毕业设计·源代码管理
麦兜*17 小时前
SpringBoot集成Redis缓存,提升接口性能的五大实战策略
spring boot·redis·缓存
康小庄18 小时前
浅谈Java中的volatile关键字
java·开发语言·jvm·spring boot·spring·jetty
vx_bisheyuange18 小时前
基于SpringBoot的海鲜市场系统
java·spring boot·后端·毕业设计
それども18 小时前
为什么要加@ResponseBody
java·开发语言·spring boot
李慕婉学姐19 小时前
【开题答辩过程】以《基于Spring Boot和大数据的医院挂号系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
大数据·spring boot·后端
invicinble21 小时前
对于springboot
java·spring boot·后端