MyBatis 延迟加载与缓存

一、延迟加载策略:按需加载,优化性能

1. 延迟加载 vs 立即加载:核心区别

  • 立即加载 :主查询(如查询用户)执行时,主动关联加载关联数据 (如用户的所有账号)。
    • 场景:多对一查询(如账号关联用户),需立即获取关联数据。
  • 延迟加载 :主查询执行时暂不加载关联数据 ,仅当程序访问关联数据时,再触发子查询。
    • 场景:一对多查询(如用户关联多个账号),减少初始查询压力。

举个小例子:

场景 :查询用户信息时不立即加载其订单,仅在需要查看订单时再触发查询。
示例:电商用户详情页先展示用户姓名、地址,点击 "查看订单" 按钮时,才加载该用户的订单列表。

2. 多对一延迟加载实现(Account → User)

步骤解析:

1、定义关联查询:主查询仅查账号表,关联用户信息通过子查询延迟加载。

XML 复制代码
<!-- 主查询:仅查账号 -->
<select id="findAll" resultMap="accountMap">
  SELECT * FROM account
</select>

<!-- 子查询:通过用户ID查用户信息 -->
<select id="findById" parameterType="int" resultType="User">
  SELECT * FROM user WHERE id = #{id}
</select>

2、配置延迟加载 :通过 association 标签指定子查询路径和参数。

XML 复制代码
<resultMap type="Account" id="accountMap">
  <association 
    property="user"          <!-- Account类中的User属性 -->
    javaType="User"          <!-- 关联对象类型 -->
    select="findById"        <!-- 子查询方法名 -->
    column="uid"             <!-- 主查询结果中用于关联的列(账号表的uid) -->
  />
</resultMap>

3、全局开启延迟加载 :在 SqlMapConfig.xml 中配置。

XML 复制代码
<settings>
  <setting name="lazyLoadingEnabled" value="true"/>   <!-- 开启延迟加载 -->
  <setting name="aggressiveLazyLoading" value="false"/> <!-- 关闭积极加载(默认会加载所有关联数据) -->
</settings>

测试验证:

javascript 复制代码
@Test
public void testLazyLoading() {
  List<Account> accounts = accountMapper.findAll();
  for (Account account : accounts) {
    System.out.println("账号金额:" + account.getMoney()); // 主查询执行时仅输出金额
    System.out.println("用户名称:" + account.getUser().getUsername()); // 首次访问user时触发子查询
  }
}

3. 一对多延迟加载实现(User → Accounts)

核心配置:

XML 复制代码
<!-- 主查询:仅查用户表 -->
<select id="findAll" resultMap="userMap">
  SELECT * FROM user
</select>

<resultMap type="User" id="userMap">
  <collection 
    property="accounts"       <!-- User类中的账号列表属性 -->
    ofType="Account"          <!-- 集合元素类型 -->
    select="com.qcbyjy.mapper.AccountMapper.findByUid" <!-- 子查询:通过用户ID查账号 -->
    column="id"                <!-- 主查询结果中的用户ID -->
  />
</resultMap>

<!-- 子查询:根据用户ID查账号 -->
<select id="findByUid" parameterType="int" resultType="Account">
  SELECT * FROM account WHERE uid = #{uid}
</select>
关键区别:
  • 多对一用 association (单个对象),一对多用 collection(集合)。
  • 子查询参数通过 column 传递主查询结果中的字段(如用户表的 id)。

二、MyBatis 缓存机制:减少数据库访问

1. 缓存的核心价值

  • 定义:将频繁查询的数据临时存储在内存中,避免重复访问数据库,提升查询速度。
  • 适用场景:读多写少、数据更新不频繁的数据(如字典表、配置信息)。

2. 一级缓存:SqlSession 级别的缓存

(1)本质与作用

  • 作用域 :基于 SqlSession 对象,同一 SqlSession 内的相同查询会直接从缓存获取结果。
  • 实现原理SqlSession 内部维护一个 HashMap,键为查询的唯一标识(SQL + 参数),值为查询结果对象。

(2)验证一级缓存

java 复制代码
@Test
public void testFirstLevelCache() {
  // 同一 SqlSession 内的两次相同查询
  User user1 = userMapper.findById(1);
  User user2 = userMapper.findById(1); // 直接从缓存获取,不执行 SQL
  System.out.println(user1 == user2); // 输出 true(对象引用相同)
}
(3)缓存失效场景
  • SqlSession 关闭或提交(commit)。
  • 执行 update/insert/delete 操作(会清空缓存)。
  • 手动调用 session.clearCache() 清空缓存。

3. 二级缓存:SqlSessionFactory 级别的缓存

(1)核心概念

  • 作用域 :基于 SqlSessionFactory,跨 SqlSession 共享缓存(如多个 SqlSession 执行相同查询)。
  • 实现条件
    1. 实体类需实现 Serializable 接口(支持序列化存储)。
    2. SqlMapConfig.xml 中开启二级缓存(默认已开启)。
    3. 在 Mapper 中配置 <cache/> 标签。

(2)配置步骤

1、 实体类实现序列化

java 复制代码
public class User implements Serializable {
  // 省略属性和方法
}

2、Mapper 中启用缓存

XML 复制代码
<mapper namespace="com.qcbyjy.mapper.UserMapper">
  <cache/> <!-- 启用二级缓存 -->
  
  <select id="findById" resultType="User" useCache="true">
    SELECT * FROM user WHERE id = #{id}
  </select>
</mapper>

3、配置缓存策略(可选)

XML 复制代码
<cache 
  eviction="LRU"       <!-- 缓存淘汰策略:LRU(最近最少使用) -->
  flushInterval="60000" <!-- 自动刷新间隔(毫秒) -->
  size="512"           <!-- 最大缓存对象数 -->
  readOnly="true"      <!-- 是否只读:true(共享对象)/ false(复制对象) -->
/>

(3)缓存优先级与刷新

  • 优先级:二级缓存 > 一级缓存 > 数据库查询。
  • 刷新机制 :执行 update/insert/delete 时,会清空对应 Mapper 的二级缓存。

(4)测试验证

java 复制代码
@Test
public void testSecondLevelCache() {
  try (SqlSession session1 = factory.openSession()) {
    UserMapper mapper1 = session1.getMapper(UserMapper.class);
    User user1 = mapper1.findById(1); // 首次查询,命中数据库
  }

  try (SqlSession session2 = factory.openSession()) {
    UserMapper mapper2 = session2.getMapper(UserMapper.class);
    User user2 = mapper2.findById(1); // 第二次查询,命中二级缓存,不执行 SQL
  }
}

三、总结:性能优化核心要点

|--------|------------------|-------------------------------------------------------------|
| 技术 | 核心作用 | 关键配置 |
| 延迟加载 | 减少初始查询数据量,提升响应速度 | lazyLoadingEnabledassociation/collectionselect 属性 |
| 一级缓存 | 减少同一会话内的重复查询 | 自动生效,无需额外配置(注意 SqlSession 生命周期) |
| 二级缓存 | 跨会话共享缓存,减少数据库压力 | 实体类序列化、<cache/> 标签、缓存策略配置 |

合理运用延迟加载和缓存,能显著提升 MyBatis 应用的性能,但需根据业务场景灵活选择,避免过度使用导致数据不一致或内存溢出。

相关推荐
Hello.Reader2 小时前
Redis 延迟监控深度指南
数据库·redis·缓存
满昕欢喜5 小时前
SQL Server从入门到项目实践(超值版)读书笔记 20
数据库·sql·sqlserver
DuelCode6 小时前
Windows VMWare Centos Docker部署Springboot 应用实现文件上传返回文件http链接
java·spring boot·mysql·nginx·docker·centos·mybatis
Hello.Reader7 小时前
Redis 延迟排查与优化全攻略
数据库·redis·缓存
荔枝吻8 小时前
【沉浸式解决问题】idea开发中mapper类中突然找不到对应实体类
java·intellij-idea·mybatis
JAVA学习通8 小时前
Mybatis--动态SQL
sql·tomcat·mybatis
m0_6239556613 小时前
Oracle使用SQL一次性向表中插入多行数据
数据库·sql·oracle
jnrjian15 小时前
Oracle RAC环境 加错数据文件 的修复 归档非归档都没问题
sql·oracle
新world16 小时前
mybatis-plus从入门到入土(二):单元测试
单元测试·log4j·mybatis
在肯德基吃麻辣烫19 小时前
《Redis》缓存与分布式锁
redis·分布式·缓存