(13)复杂查询

🎯 场景说明

  • 多对一(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 会这样执行:

  1. 先执行主查询

    sql 复制代码
      SELECT id, product, user_id FROM order

    得到两行结果:(1, "iPhone", 100)(2, "iPad", 200)

  2. 对每一行,再发起一次子查询

    • 第一行:调用 UserMapper.selectById(100)

      sql 复制代码
      SELECT * FROM user WHERE id = 100
    • 第二行:调用 UserMapper.selectById(200)

      sql 复制代码
      SELECT * FROM user WHERE id = 200
  3. 组装对象

    java 复制代码
    Order 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) + 手动组装的方式,但底层原理相同。

相关推荐
浩瀚地学1 小时前
【Java】ArrayList
java·开发语言·经验分享·笔记
阿杰同学1 小时前
Java 设计模式 面试题及答案整理,最新面试题
java·开发语言·设计模式
这样の我1 小时前
java 模拟chrome指纹 处理tls extension顺序
java·开发语言·chrome
LeonIter1 小时前
《以日为鉴》读书随笔
笔记
Genevieve_xiao1 小时前
【数据结构与算法】【xjtuse】面向考纲学习(下)
java·数据结构·学习·算法
curd_boy1 小时前
IM 顶层设计
websocket·架构·信息与通信
4311媒体网1 小时前
php和c++哪个更好学?C++难学吗?
java·c++·php
毕设源码-朱学姐1 小时前
【开题答辩全过程】以 基于SpringBoot的流行音乐网站的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
SadSunset1 小时前
(12)基于注解实现的sql
mybatis