🎯 场景说明
- 多对一(Many-to-One) :多个
Order属于一个User→ Order → User - 一对多(One-to-Many) :一个
User有多个Order→ User → List
✅ 一、多对一(Many-to-One)------ 嵌套查询写法
1. Java 实体
java
public class Order {
private Long id;
private String product;
private User user; // 多对一:每个订单属于一个用户
}
public class User {
private Long id;
private String name;
}
2. Mapper XML(嵌套查询)
xml
<resultMap id="OrderWithUserResultMap" type="Order">
<id property="id" column="id"/>
<result property="product" column="product"/>
<!-- 嵌套查询:通过 userId 去查 User -->
<association property="user"
javaType="User"
select="com.example.mapper.UserMapper.selectById"
column="user_id" />
</resultMap>
<select id="selectOrdersWithUser" resultMap="OrderWithUserResultMap">
SELECT id, product, user_id FROM order
</select>
🔍 逐行解释
1. <resultMap id="OrderWithUserResultMap" type="Order">
定义一个叫
OrderWithUserResultMap的映射规则,目标类型是Order类。
2. <id property="id" column="id"/>
> 把 SQL 查询结果中的 `id` 列 → 赋值给 `Order` 对象的 `id` 属性。MyBatis 要求主键必须用 <id> 标签映射,不能用 <result>。
3. <result property="product" column="product"/>
把
product列 → 赋值给Order.product。
4. 关键部分 👇
xml
<association property="user"
javaType="User"
select="com.example.mapper.UserMapper.selectById"
column="user_id" />
这四行的意思是:
| 属性 | 含义 |
|---|---|
property="user" |
当前 Order 对象里有个字段叫 user(类型是 User) |
javaType="User" |
明确告诉 MyBatis 这个字段的类型是 User |
select="..." |
去调用另一个 Mapper 方法 :UserMapper.selectById 来查用户 |
column="user_id" |
把当前行的 user_id 值,作为参数传给上面那个方法 |
🔄 执行过程(重点!)
假设 order 表有 2 条数据:
id | product | user_id
1 | iPhone | 100
2 | iPad | 200
当你调用:
java
List<Order> orders = orderMapper.selectOrdersWithUser();
MyBatis 会这样执行:
-
先执行主查询:
sqlSELECT id, product, user_id FROM order得到两行结果:
(1, "iPhone", 100)和(2, "iPad", 200) -
对每一行,再发起一次子查询:
-
第一行:调用
UserMapper.selectById(100)sqlSELECT * FROM user WHERE id = 100 -
第二行:调用
UserMapper.selectById(200)sqlSELECT * FROM user WHERE id = 200
-
-
组装对象:
javaOrder order1 = new Order(); order1.setId(1); order1.setProduct("iPhone"); order1.setUser( userFromDB1 ); // ← 从子查询拿到
⚠️ 问题来了:这就是 N+1 查询
- 主查询:1 次
- 子查询:N 次(N = 订单数量)
- 总共:1 + N 条 SQL
如果查 1000 个订单 → 执行 1001 条 SQL → 数据库压力巨大!
✅ 正确做法(大厂推荐):用 JOIN 一次性查完
xml
<resultMap id="OrderWithUserResultMap" type="Order">
<id property="id" column="order_id"/>
<result property="product" column="product"/>
<!-- 直接映射关联对象,不发起新查询 -->
<association property="user" javaType="User">
<id property="id" column="user_id"/>
<result property="name" column="user_name"/>
</association>
</resultMap>
<select id="selectOrdersWithUser" resultMap="OrderWithUserResultMap">
SELECT
o.id AS order_id,
o.product,
u.id AS user_id,
u.name AS user_name
FROM order o
LEFT JOIN user u ON o.user_id = u.id
</select>
✅ 只执行 1 条 SQL ,性能好一万倍!
相当于在对象的每个值都拿出来继续映射了,而不用再一次次查询分别封装,因为只写了一句SQL,这两个信息通过JOIN 一次性查完
📌 总结
| 你看到的写法 | 实际含义 | 是否推荐 |
|---|---|---|
<association select="..."> |
每行都发起一次新查询(N+1) | ❌ 仅用于查单条记录 |
<association> + JOIN |
一次查完所有数据 | ✅ 列表查询必须用这个 |
💡 记住:
select="..."是"懒加载式"查关联对象 → 慎用- 想高性能?永远优先用 JOIN + 嵌套结果映射
🔍column="user_id"会作为参数传给UserMapper.selectById(Long id)
✅ 二、一对多(One-to-many)------ 嵌套查询写法
1. Java 实体
java
public class User {
private Long id;
private String name;
private List<Order> orders; // 一对多:用户有多个订单
}
2. Mapper XML
xml
<resultMap id="UserWithOrdersResultMap" type="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<!-- 嵌套查询:通过 user.id 去查所有订单 -->
<collection property="orders"
ofType="Order"
select="com.example.mapper.OrderMapper.selectByUserId"
column="id" />
</resultMap>
<select id="selectUsersWithOrders" resultMap="UserWithOrdersResultMap">
SELECT id, name FROM user
</select>
🔍
column="id"会作为参数传给OrderMapper.selectByUserId(Long userId)
对比两个标签:
嵌套查询:
<association>
⚠️ 三、嵌套查询的致命问题:N+1 查询(跟上面一样)
- 查 10 个用户 → 执行 1 次主查询 + 10 次子查询(查订单)→ 共 11 条 SQL
- 数据量大时,数据库压力爆炸,响应慢
❌ 这是大厂 Code Review 直接打回的写法!
✅ 四、大厂推荐方案:嵌套结果(JOIN + collection/association)
一对多示例(User + Orders):
xml
<resultMap id="UserWithOrdersResultMap" type="User">
<id property="id" column="user_id"/>
<result property="name" column="user_name"/>
<collection property="orders" ofType="Order">
<id property="id" column="order_id"/>
<result property="product" column="product"/>
</collection>
</resultMap>
<select id="selectUsersWithOrders" resultMap="UserWithOrdersResultMap">
SELECT
u.id AS user_id,
u.name AS user_name,
o.id AS order_id,
o.product
FROM user u
LEFT JOIN order o ON u.id = o.user_id
ORDER BY u.id
</select>
✅ 只执行 1 条 SQL ,MyBatis 自动去重 + 组装 List
✅ 必须加
ORDER BY主表主键,否则去重可能出错
本质上是两个复杂有什么问题?
本质上查询多次是因为调用了另外一条sql,而通过一条sql 加上join就可以解决,剩下的就是数据的映射问题。
📌 五、什么时候可以用嵌套查询?
| 场景 | 是否可用 |
|---|---|
| 数据量极小(如查 1 个用户及其配置) | ✅ 可用 |
| 列表页(查 N 条主记录 + 关联数据) | ❌ 禁止!用 JOIN |
| 关联对象非常大(避免 JOIN 膨胀) | ⚠️ 谨慎,考虑分两次查 |
✅ 六、总结(背下来)
| 方式 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|
| 嵌套查询(select) | 写法简单 | N+1 性能灾难 | ❌ 仅限单条查询 |
| 嵌套结果(JOIN) | 1 条 SQL,高效 | SQL 稍复杂 | ✅ 大厂标准做法 |
💡 口诀 :
"列表用 JOIN,单查可嵌套;
N+1 是红线,上线必被打回。"
如果你用的是 MyBatis-Plus ,它也提供了 @TableField(exist = false) + 手动组装的方式,但底层原理相同。