MyBatis延迟加载(Lazy Loading)之“关联查询”深度解析与实践
引言
延迟加载是MyBatis优化性能的核心技术之一,特别适用于处理对象关联关系。当主实体关联的子实体数据量较大或访问频率较低时,延迟加载能显著减少不必要的数据库查询,提升系统性能。
完整代码实现
1. XML映射配置
<!-- 学生映射配置 -->
<resultMap id="studentMap" type="com.test.entity.Student"><id property="id" column="id"/><result property="name" column="name"/><association property="clazz" javaType="com.test.entity.Class"column="cid"select="com.test.repository.ClassRepository.findByClaId"/>
</resultMap><select id="findByStuId" parameterType="java.lang.Integer" resultMap="studentMap">SELECT * FROM student WHERE id = #{id}
</select><!-- 班级映射配置 -->
<select id="findByClaId" parameterType="java.lang.Integer" resultType="com.test.entity.Class">SELECT * FROM class WHERE id = #{id}
</select>
2. 仓库接口定义
// 学生仓库接口
public interface StudentRepository {Student findByStuId(Integer id);
}// 班级仓库接口
public interface ClassRepository {Class findByClaId(Integer id);
}
逐行解析配置
<resultMap id="studentMap" type="com.test.entity.Student">
<resultMap>
:定义ORM映射规则id="studentMap"
:映射规则唯一标识符type="com.test.entity.Student"
:目标实体类全路径
<id property="id" column="id"/>
<result property="name" column="name"/>
<id>
:主键字段映射(数据库id列 → 实体id属性)<result>
:普通字段映射(数据库name列 → 实体name属性)
<association property="clazz" javaType="com.test.entity.Class"column="cid"select="com.test.repository.ClassRepository.findByClaId"/>
property="clazz"
:映射到Student实体的clazz属性javaType="com.test.entity.Class"
:关联属性的完整类型column="cid"
:传递到关联查询的参数列(当前查询的cid字段值)select="..."
:指定延迟加载的查询方法(关键配置)
<select id="findByStuId" resultMap="studentMap">SELECT * FROM student WHERE id = #{id}
</select>
- 主查询:根据学生ID获取基本信息(不包含班级数据)
延迟加载执行流程
延迟加载核心特性
-
按需加载机制
- 初始查询仅获取学生基础数据(不含关联对象)
- 班级数据在首次调用
getClazz()
时动态加载 - 避免不必要的数据传输和内存占用
-
N+1查询模式
- 1次主查询获取学生列表
- N次关联查询获取每个学生的班级数据
- 示例:查询10个学生 → 1(主查询)+10(班级查询)
-
关联查询分离
- 主查询与关联查询完全解耦
- 各查询可独立优化和复用
- 通过
select
属性指定关联加载方法
延迟加载 vs JOIN查询
维度 | 延迟加载 | JOIN查询 |
---|---|---|
查询次数 | 1 + N(按需加载) | 1次(复杂JOIN) |
数据量 | 首次响应快,传输量小 | 单次传输量大 |
内存占用 | 初始内存占用低 | 一次性加载所有关联数据 |
适用场景 | 关联数据访问率<30% | 需要立即使用所有关联数据 |
性能瓶颈 | N+1问题(批量操作时) | JOIN复杂度 |
代码维护 | 逻辑清晰,关联配置解耦 | SQL复杂度高 |
最佳实践与优化策略
1. 全局配置启用(mybatis-config.xml)
<settings><!-- 启用延迟加载 --><setting name="lazyLoadingEnabled" value="true"/><!-- 禁用激进加载 --><setting name="aggressiveLazyLoading" value="false"/><!-- 按需加载触发方法 --><setting name="lazyLoadTriggerMethods" value="equals,clone"/>
</settings>
2. 批量加载优化
<association property="clazz" select="com.test.repository.ClassRepository.findById"fetchType="lazy" <!-- 显式声明加载方式 -->column="cid"/>
3. 解决N+1问题
- 批量预加载:通过
@Fetch
注解配置批量加载@Fetch(FetchMode.SUBSELECT) private Class clazz;
- 智能判断:根据业务场景混合使用JOIN和延迟加载
- 二级缓存:对频繁访问的关联实体启用缓存
4. 方法命名规范
如示例所示,采用findByStuId
/findByClaId
的明确命名:
- 清晰区分主查询和关联查询
- 避免方法重载导致的歧义
- 提高代码可读性和维护性
典型应用场景
-
列表-详情页模式
- 列表页:仅加载学生基本信息(
findByStuId
) - 详情页:点击时加载班级数据(触发
getClazz()
)
- 列表页:仅加载学生基本信息(
-
大对象关联
- 学生关联班级简历(大文本字段)
- 初始不加载,需要时再获取
-
多级嵌套关联
逐级延迟加载避免深度JOIN
-
微服务架构
- 学生服务和班级服务分离时
- 通过延迟加载实现跨服务数据聚合
总结
MyBatis延迟加载通过精妙的<association>
配置和按需加载机制,在保证功能完整性的同时优化了系统性能。关键点在于:
- 使用
select
属性分离关联查询 - 通过明确的方法命名(如
findByXxxId
)增强可读性 - 合理配置全局延迟加载策略
- 根据业务场景选择加载策略(延迟加载/JOIN/批量加载)
正确使用延迟加载可使应用在数据层获得10x性能提升,特别适用于大型企业级应用和高并发场景。