MyBatis 关联查询延迟加载实战案例
作者:Java成神之路-
前言
在MyBatis开发中,单表查询我们直接用resultType就能轻松搞定,但遇到一对多、一对一关联查询(如1个用户对应多个订单、1个订单对应1个用户),且需要优化性能时,就必须用到resultMap配合延迟加载。
一、核心基础概念
1. 业务场景:一对多关系
最经典的场景:用户表(一) ↔ 订单表(多)
- 1个用户可以拥有多个订单
- 1个订单只属于1个用户
对应Java实体类设计:
// 用户实体(一的一方)
@Data
public class User {
private Integer id;
private String username;
// 一对多:存放当前用户的所有订单
private List<Order> orderList;
}
// 订单实体(多的一方)
@Data
public class Order {
private Integer id;
private String orderNo;
// 多对一:存放订单所属的用户
private User user;
}2. 立即加载 VS 延迟加载(懒加载)
这是性能优化的核心,也是配置fetchType="lazy"的意义:
| 加载方式 | 含义 | 优缺点 |
|---|---|---|
| 立即加载 | 查询数据时,同时查询关联数据 | 代码简单;查询冗余,性能差 |
| 延迟加载 | 查询时只查本身数据,用到关联数据时才查询 | 减少冗余SQL,性能高;需要额外配置 |
通俗理解:延迟加载就是按需加载,用不到的数据绝不查询!
二、为什么要写 resultMap?(关联查询专用)
1. 单表查询
日常单查用户/订单,一行代码搞定,完全不用复杂配置:
<!-- 单表查询:resultType 直接映射,无需配置resultMap -->
<select id="selectById" resultType="com.itheima.pojo.User">
select * from tb_user where id = #{id}
</select>✅ 适用场景:纯单表查询,无关联关系
2. 必须用复杂配置的场景
当需求变为:查询主数据 + 按需加载关联数据
MyBatis规定:一对多/一对一关联 + 延迟加载,必须使用 resultMap 配置
这是固定语法!
三、一对一延迟加载(association 标签)
一对一场景:订单表 ↔ 用户表(1个订单只属于1个用户),MyBatis用association标签实现一对一延迟加载!
1. 一对一核心配置
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.OrderMapper">
<!-- 一对一结果映射 -->
<resultMap id="orderResultMap" type="order" autoMapping="true">
<id property="id" column="id"/>
<result property="orderNo" column="order_no"/>
<!-- 一对一关联配置:延迟加载用户信息 -->
<association
property="user"
javaType="user"
select="com.itheima.mapper.UserMapper.selectById"
column="user_id"
fetchType="lazy">
</association>
</resultMap>
<!-- 根据id查询订单 -->
<select id="selectById" resultMap="orderResultMap">
select * from tb_order where id = #{id}
</select>
</mapper>✨ association 标签5大核心属性
property:实体类中关联对象属性名(Order类中的user)javaType:关联对象的实体类型select:延迟加载调用的查询方法全路径column:传递给子查询的参数(订单表的user_id)fetchType="lazy":开启一对一延迟加载
四、一对多延迟加载(collection 标签)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.UserMapper">
<!-- 1. 定义复杂映射规则:id唯一标识,type映射实体类,autoMapping自动匹配字段 -->
<resultMap id="userResultMap" type="user" autoMapping="true">
<!-- 2. 主键映射(规范写法,可省略) -->
<id property="id" column="id"/>
<!-- 普通字段映射(autoMapping=true可自动匹配,可省略) -->
<result property="username" column="username"/>
<!-- 3. 核心:一对多关联映射,延迟加载配置 -->
<collection
property="orderList"
ofType="order"
select="com.itheima.mapper.OrderMapper.findByUid"
column="id"
fetchType="lazy">
</collection>
</resultMap>
<!-- 4. 查询用户,绑定上面的映射规则 -->
<select id="selectById" resultMap="userResultMap">
select * from tb_user where id = #{id}
</select>
</mapper>✨ collection 标签5大核心属性
这是一对多配置的灵魂,记住这5个属性,再也不会写错:
property:实体类中集合属性的名称(必须和User类中的orderList一致)ofType:集合中存储的实体类型select:延迟加载时执行的子查询方法全路径column:传递给子查询的参数列(用户id)fetchType="lazy":开启延迟加载(默认是立即加载)
五、完整实战代码
1. MyBatis全局配置(开启延迟加载总开关)
<settings>
<!-- 开启延迟加载总开关 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 关闭积极加载(3.4.1后默认false,配置更保险) -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>① lazyLoadingEnabled(总开关)
作用:控制 是否开启延迟加载
默认值:false → 关闭延迟加载,默认使用立即加载
人话:查询主数据时,直接把关联数据一起查出来,不等待调用
② aggressiveLazyLoading(触发方式)
作用:控制 延迟加载的触发条件
默认值:false
人话:只有开启了延迟加载(lazyLoadingEnabled=true),这个配置才有用!
true:调用对象任何方法(toString/equals/getter)都触发加载
false:只调用关联属性的 getter 方法才触发加载(最标准的懒加载)
2. 子查询Mapper配置
一对多/一对一依赖的子查询方法必须单独定义:
<mapper namespace="com.itheima.mapper.OrderMapper">
<!-- 根据用户id查询订单:供一对多延迟加载调用 -->
<select id="findByUid" resultType="com.itheima.pojo.Order">
select * from tb_order where user_id = #{userId}
</select>
</mapper>3. Mapper接口
// UserMapper
public interface UserMapper {
User selectById(Integer id);
}
// OrderMapper
public interface OrderMapper {
List<Order> findByUid(Integer userId);
Order selectById(Integer id);
}六、延迟加载执行流程
1. 一对一执行流程
@Test
public void testOneToOne(){
// 1. 仅查询订单:只执行1条SQL → select * from tb_order where id=1
Order order = orderMapper.selectById(1);
System.out.println("订单编号:" + order.getOrderNo());
// 2. 调用user:触发一对一延迟加载,查询用户信息
System.out.println("订单所属用户:" + order.getUser());
}2. 一对多执行流程
@Test
public void testLazyLoad(){
// 1. 仅查询用户:只执行1条SQL → select * from tb_user where id=1
User user = userMapper.selectById(1);
System.out.println("查询到用户:" + user.getUsername());
// 2. 未使用订单数据:不执行订单查询SQL
// 3. 调用orderList:触发延迟加载,执行订单SQL → select * from tb_order where user_id=1
System.out.println("用户订单:" + user.getOrderList());
}完美实现按需加载!
七、注意事项
- 忘记开启全局延迟加载
全局开关 lazyLoadingEnabled(默认 false)控制所有未显式设置 fetchType 的关联; 局部属性 fetchType="lazy" 可以独立让单个关联延迟加载,不受全局开关影响。 - 子查询方法不存在/路径写错
select属性必须是全限定名,因为这是两个mapper.xml文件。 - 实体类属性名不匹配
配置的property属性必须和实体类属性名完全一致。 - 标签区分
一对多用collection,一对一用association,不可混用!
八、延迟加载的优势
- 提升性能:避免查询不需要的关联数据,减少数据库压力
- 解决N+1问题:查询数据时,不会自动查询关联数据
- 灵活适配业务:只查主数据时,无冗余SQL
- 通用适配:同时支持一对多、一对一关联场景
九、总结
- 单表查询:用
resultType,无需任何复杂配置(日常开发主流); - 一对一关联:必须用
resultMap + association; - 一对多关联:必须用
resultMap + collection; - 延迟加载核心:
fetchType="lazy"+ 全局配置开关 + 按需加载; - 核心标签:association(一对一)、collection(一对多),语法结构高度一致,仅标签和类型属性不同。
到此这篇关于MyBatis 关联查询延迟加载案例的文章就介绍到这了,更多相关mybatis 延迟加载内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
