(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·spring boot·mysql·vue·mybatis·uniapp
im_AMBER1 天前
weather-app开发手记 04 AntDesign组件库使用解析 | 项目设计困惑
开发语言·前端·javascript·笔记·学习·react.js
lkbhua莱克瓦241 天前
MySQL介绍
java·开发语言·数据库·笔记·mysql
武昌库里写JAVA1 天前
在iview中使用upload组件上传文件之前先做其他的处理
java·vue.js·spring boot·后端·sql
董世昌411 天前
什么是事件冒泡?如何阻止事件冒泡和浏览器默认事件?
java·前端
好度1 天前
配置java标准环境?(详细教程)
java·开发语言
teacher伟大光荣且正确1 天前
关于Qt QReadWriteLock(读写锁) 以及 QSettings 使用的问题
java·数据库·qt
nightseventhunit1 天前
base64字符串String.getByte导致OOM Requested array size exceeds VM limit
java·oom
悟能不能悟1 天前
java map判断是否有key,get(key)+x,否则put(key,x)的新写法
java·开发语言
webbodys1 天前
Python文件操作与异常处理:构建健壮的应用程序
java·服务器·python