MyBatis 一对多和多对一案例

以下是一个完整的 用户 → 订单 → 订单明细 → 商品三级嵌套一对多 案例,包含数据库设计、Java 实体类和 MyBatis 实现:


场景描述

  1. 用户可以有多个订单(一对多)
  2. 每个订单可以包含多个订单明细(一对多)
  3. 每个订单明细关联一个商品(多对一)

数据库表结构

sql 复制代码
-- 用户表
CREATE TABLE user (
  id INT PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(50) NOT NULL
);

-- 订单表
CREATE TABLE order (
  id INT PRIMARY KEY AUTO_INCREMENT,
  user_id INT NOT NULL,
  order_no VARCHAR(20),
  FOREIGN KEY (user_id) REFERENCES user(id)
);

-- 商品表
CREATE TABLE product (
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(100) NOT NULL,
  price DECIMAL(10,2)
);

-- 订单明细表(连接订单和商品)
CREATE TABLE order_item (
  id INT PRIMARY KEY AUTO_INCREMENT,
  order_id INT NOT NULL,
  product_id INT NOT NULL,
  quantity INT NOT NULL,
  FOREIGN KEY (order_id) REFERENCES order(id),
  FOREIGN KEY (product_id) REFERENCES product(id)
);

Java 实体类

java 复制代码
// User.java
public class User {
    private Integer id;
    private String username;
    private List<Order> orders;  // 一级一对多
    // getters/setters
}

// Order.java
public class Order {
    private Integer id;
    private String orderNo;
    private List<OrderItem> items;  // 二级一对多
    // getters/setters
}

// OrderItem.java
public class OrderItem {
    private Integer id;
    private Integer quantity;
    private Product product;  // 三级多对一
    // getters/setters
}

// Product.java
public class Product {
    private Integer id;
    private String name;
    private BigDecimal price;
    // getters/setters
}

MyBatis 实现方案

方案一:联表查询(适合数据量小的情况)

xml 复制代码
<!-- UserMapper.xml -->
<resultMap id="userDetailMap" type="User">
    <id property="id" column="user_id"/>
    <result property="username" column="username"/>
    
    <!-- 一级一对多:用户 → 订单 -->
    <collection property="orders" ofType="Order">
        <id property="id" column="order_id"/>
        <result property="orderNo" column="order_no"/>
        
        <!-- 二级一对多:订单 → 订单明细 -->
        <collection property="items" ofType="OrderItem">
            <id property="id" column="item_id"/>
            <result property="quantity" column="quantity"/>
            
            <!-- 三级多对一:订单明细 → 商品 -->
            <association property="product" javaType="Product">
                <id property="id" column="product_id"/>
                <result property="name" column="product_name"/>
                <result property="price" column="price"/>
            </association>
        </collection>
    </collection>
</resultMap>

<select id="findUserWithDetails" resultMap="userDetailMap">
    SELECT 
        u.id AS user_id,
        u.username,
        o.id AS order_id,
        o.order_no,
        oi.id AS item_id,
        oi.quantity,
        p.id AS product_id,
        p.name AS product_name,
        p.price
    FROM user u
    LEFT JOIN order o ON u.id = o.user_id
    LEFT JOIN order_item oi ON o.id = oi.order_id
    LEFT JOIN product p ON oi.product_id = p.id
    WHERE u.id = #{userId}
</select>

方案二:嵌套查询(适合大数据量,支持延迟加载)

xml 复制代码
<!-- UserMapper.xml -->
<resultMap id="userMap" type="User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <!-- 一级嵌套查询 -->
    <collection 
        property="orders" 
        column="id" 
        ofType="Order"
        select="findOrdersByUserId"/>
</resultMap>

<select id="findUserWithDetails" resultMap="userMap">
    SELECT * FROM user WHERE id = #{userId}
</select>

<!-- 二级查询:订单及明细 -->
<resultMap id="orderMap" type="Order">
    <id property="id" column="id"/>
    <result property="orderNo" column="order_no"/>
    <!-- 二级嵌套查询 -->
    <collection 
        property="items" 
        column="id" 
        ofType="OrderItem"
        select="findItemsByOrderId"/>
</resultMap>

<select id="findOrdersByUserId" resultMap="orderMap">
    SELECT * FROM order WHERE user_id = #{userId}
</select>

<!-- 三级查询:订单明细及商品 -->
<resultMap id="itemMap" type="OrderItem">
    <id property="id" column="id"/>
    <result property="quantity" column="quantity"/>
    <!-- 三级嵌套查询 -->
    <association 
        property="product" 
        column="product_id" 
        javaType="Product"
        select="findProductById"/>
</resultMap>

<select id="findItemsByOrderId" resultMap="itemMap">
    SELECT * FROM order_item WHERE order_id = #{orderId}
</select>

<!-- 商品查询 -->
<select id="findProductById" resultType="Product">
    SELECT * FROM product WHERE id = #{productId}
</select>

执行效果

查询结果会生成如下嵌套结构:

java 复制代码
User{
  id=1,
  username="张三",
  orders=[
    Order{
      id=1001,
      orderNo="20230815001",
      items=[
        OrderItem{
          id=5001,
          quantity=2,
          product=Product{id=101, name="手机", price=2999.00}
        },
        OrderItem{
          id=5002,
          quantity=1,
          product=Product{id=102, name="耳机", price=399.00}
        }
      ]
    },
    Order{
      id=1002,
      orderNo="20230816002",
      items=[
        OrderItem{
          id=5003,
          quantity=3,
          product=Product{id=103, name="充电宝", price=199.00}
        }
      ]
    }
  ]
}

性能优化建议

  1. 联表查询方案 • 优点:单次查询获取全部数据 • 缺点:结果集会有重复数据(用户和订单信息会重复出现在每条订单明细记录中) • 适用场景:数据量较小的系统

  2. 嵌套查询方案 • 优点:按需加载,支持延迟加载 • 缺点:产生 1+N+M 次查询(用户查询1次 + 订单查询N次 + 商品查询M次) • 优化方法:配置批量加载

    xml 复制代码
    <!-- mybatis-config.xml -->
    <settings>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>

关键点总结

  1. 标签嵌套规则 • 一级一对多:<collection> 嵌套在 <resultMap> • 二级一对多:<collection> 嵌套在父级 <collection> • 多对一关系:使用 <association>

  2. 字段别名规范

    sql 复制代码
    -- 联表查询时使用明确别名
    u.id AS user_id,
    o.id AS order_id,
    oi.id AS item_id
  3. 参数传递 • 嵌套查询中通过 column 属性传递参数时,确保子查询方法的参数名与传递的列名一致

通过这种多级嵌套方案,可以处理任意深度的对象关联关系,建议根据实际业务复杂度选择最合适的实现方式。

相关推荐
知识分享小能手12 分钟前
CSS3学习教程,从入门到精通,CSS3 浮动与清除浮动语法知识点及案例代码(14)
前端·css·后端·学习·html·css3·html5
Answer_ism6 小时前
【SpringMVC】SpringMVC拦截器,统一异常处理,文件上传与下载
java·开发语言·后端·spring·tomcat
盖世英雄酱581369 小时前
JDK24 它来了,抗量子加密
java·后端
Asthenia04129 小时前
无感刷新的秘密:Access Token 和 Refresh Token 的那些事儿
前端·后端
Asthenia041210 小时前
面试复盘:聊聊epoll的原理、以及其相较select和poll的优势
后端
luckyext10 小时前
SQLServer列转行操作及union all用法
运维·数据库·后端·sql·sqlserver·运维开发·mssql
Asthenia041210 小时前
ES:倒排索引的原理与写入分析
后端
圈圈编码11 小时前
Spring常用注解汇总
java·后端·spring
stark张宇11 小时前
PHP多版本共存终极填坑指南:一台服务器部署多实例的最佳实践
后端·php
Lian_Aseubel12 小时前
Springboot整合Netty简单实现1对1聊天(vx小程序服务端)
java·spring boot·后端