java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > MyBatis 关联查询

MyBatis的关联查询实现(一对一、一对多、多对多)

作者:AA-代码批发V哥

本文主要介绍了MyBatis的关联查询实现,包括一对一、一对多和多对多关系的实现方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

实际开发中数据库表之间往往存在关联关系(如用户与订单、订单与商品),MyBatis的关联查询用于处理这些关系,将多表数据映射为Java对象的关联关系,相比JDBC手动处理结果集拼接,MyBatis通过resultMapassociationcollection标签,能自动完成关联数据的映射。本文我将系统讲解MyBatis关联查询的核心实现,包括一对一、一对多、多对多关系,并结合实例解析查询方式与优化技巧。

一、关联查询的基本概念

1.1 数据库表关联关系

数据库表的关联关系主要有三种:

1.2 MyBatis关联查询的核心

MyBatis通过resultMap实现关联查询,核心标签:

关联查询有两种实现方式:

后续将通过案例详细对比这两种方式的优缺点。

二、一对一关联查询

以“用户(user)与身份证(id_card)”为例,一个用户对应一个身份证,实现一对一关联查询。

2.1 数据库表设计

-- 用户表
CREATE TABLE `user` (
  `id` int NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL,
  `age` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 身份证表(与用户一对一关联)
CREATE TABLE `id_card` (
  `id` int NOT NULL AUTO_INCREMENT,
  `card_no` varchar(20) NOT NULL, -- 身份证号
  `user_id` int NOT NULL, -- 关联用户ID(外键)
  PRIMARY KEY (`id`),
  UNIQUE KEY `user_id` (`user_id`) -- 一对一:user_id唯一
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2.2 实体类设计

// 用户类(包含一个身份证对象)
@Data
public class User {
    private Integer id;
    private String username;
    private Integer age;
    // 一对一关联:用户包含一个身份证
    private IdCard idCard; 
}

// 身份证类
@Data
public class IdCard {
    private Integer id;
    private String cardNo;
    private Integer userId;
}

2.3 一对一查询实现

2.3.1 方式1:连接查询(推荐)

通过JOIN语句一次性查询用户和身份证数据,再通过association映射关联对象。

Mapper接口

// 根据用户ID查询用户及关联的身份证
User selectUserWithIdCardById(Integer id);

Mapper XML

<!-- 定义resultMap:映射用户及身份证 -->
<resultMap id="UserWithIdCardMap" type="User">
    <!-- 用户表字段映射 -->
    <id column="id" property="id"/>
    <result column="username" property="username"/>
    <result column="age" property="age"/>
    
    <!-- 一对一关联:association映射IdCard对象 -->
    <association property="idCard" javaType="IdCard">
        <id column="card_id" property="id"/> <!-- 注意:避免与user.id字段冲突 -->
        <result column="card_no" property="cardNo"/>
        <result column="user_id" property="userId"/>
    </association>
</resultMap>

<!-- 连接查询:一次性查询用户和身份证 -->
<select id="selectUserWithIdCardById" resultMap="UserWithIdCardMap">
    SELECT 
        u.id, u.username, u.age,
        ic.id AS card_id, ic.card_no, ic.user_id
    FROM user u
    LEFT JOIN id_card ic ON u.id = ic.user_id
    WHERE u.id = #{id}
</select>

核心说明

2.3.2 方式2:嵌套查询

先查询用户数据,再通过用户ID查询身份证(分两次查询)。

步骤1:查询身份证的Mapper

// IdCardMapper接口
IdCard selectById(Integer id);
<select id="selectById" resultType="IdCard">
    SELECT id, card_no, user_id FROM id_card WHERE id = #{id}
</select>

步骤2:查询用户并嵌套查询身份证

<resultMap id="UserWithIdCardNestedMap" type="User">
    <id column="id" property="id"/>
    <result column="username" property="username"/>
    <result column="age" property="age"/>
    
    <!-- 嵌套查询:通过select属性指定查询关联对象的方法 -->
    <association 
        property="idCard" 
        javaType="IdCard"
        column="id" <!-- 将用户id作为参数传递给关联查询 -->
        select="com.example.mapper.IdCardMapper.selectByUserId"/> <!-- 关联查询的Mapper方法 -->
</resultMap>

<select id="selectUserWithIdCardNestedById" resultMap="UserWithIdCardNestedMap">
    SELECT id, username, age FROM user WHERE id = #{id}
</select>

核心说明

2.3.3 两种方式对比

方式优点缺点适用场景
连接查询单轮查询,性能好SQL较复杂(多表JOIN)关联数据必须查询,且表数据量不大
嵌套查询SQL简单,逻辑清晰多轮查询(N+1问题),性能较差关联数据可选查询(按需加载)

三、一对多关联查询

以“用户(user)与订单(order)”为例,一个用户可有多笔订单,实现一对多关联查询。

3.1 数据库表设计

-- 订单表(与用户一对多关联)
CREATE TABLE `order` (
  `id` int NOT NULL AUTO_INCREMENT,
  `order_no` varchar(20) NOT NULL, -- 订单号
  `total_amount` decimal(10,2) NOT NULL, -- 总金额
  `user_id` int NOT NULL, -- 关联用户ID
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

3.2 实体类设计

// 用户类(包含订单列表)
@Data
public class User {
    private Integer id;
    private String username;
    private Integer age;
    // 一对多关联:用户包含多个订单
    private List<Order> orders; 
}

// 订单类
@Data
public class Order {
    private Integer id;
    private String orderNo;
    private BigDecimal totalAmount;
    private Integer userId;
}

3.3 一对多查询实现

连接查询为例(推荐,单轮查询性能更好):

Mapper接口

// 查询用户及关联的所有订单
User selectUserWithOrdersById(Integer id);

Mapper XML

<!-- 定义resultMap:映射用户及订单列表 -->
<resultMap id="UserWithOrdersMap" type="User">
    <!-- 用户表字段 -->
    <id column="id" property="id"/>
    <result column="username" property="username"/>
    <result column="age" property="age"/>
    
    <!-- 一对多关联:collection映射订单列表 -->
    <collection property="orders" ofType="Order"> <!-- ofType指定集合元素类型 -->
        <id column="order_id" property="id"/> <!-- 订单ID(别名避免冲突) -->
        <result column="order_no" property="orderNo"/>
        <result column="total_amount" property="totalAmount"/>
        <result column="user_id" property="userId"/>
    </collection>
</resultMap>

<!-- 连接查询:用户与订单 -->
<select id="selectUserWithOrdersById" resultMap="UserWithOrdersMap">
    SELECT 
        u.id, u.username, u.age,
        o.id AS order_id, o.order_no, o.total_amount, o.user_id
    FROM user u
    LEFT JOIN `order` o ON u.id = o.user_id
    WHERE u.id = #{id}
</select>

核心说明

四、多对多关联查询

以“学生(student)与课程(course)”为例,一个学生可选多门课程,一门课程可有多个学生,通过中间表student_course实现关联。

4.1 数据库表设计

-- 学生表
CREATE TABLE `student` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 课程表
CREATE TABLE `course` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 中间表(多对多关联)
CREATE TABLE `student_course` (
  `id` int NOT NULL AUTO_INCREMENT,
  `student_id` int NOT NULL,
  `course_id` int NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_stu_course` (`student_id`,`course_id`) -- 避免重复关联
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

4.2 实体类设计

// 学生类(包含课程列表)
@Data
public class Student {
    private Integer id;
    private String name;
    // 多对多关联:学生包含多个课程
    private List<Course> courses;
}

// 课程类
@Data
public class Course {
    private Integer id;
    private String name;
}

4.3 多对多查询实现

多对多查询本质是一对多的扩展(通过中间表连接),以连接查询为例:

Mapper接口

// 查询学生及所选课程
Student selectStudentWithCoursesById(Integer id);

Mapper XML

<resultMap id="StudentWithCoursesMap" type="Student">
    <id column="id" property="id"/>
    <result column="name" property="name"/>
    
    <!-- 多对多:collection映射课程列表 -->
    <collection property="courses" ofType="Course">
        <id column="course_id" property="id"/>
        <result column="course_name" property="name"/>
    </collection>
</resultMap>

<select id="selectStudentWithCoursesById" resultMap="StudentWithCoursesMap">
    SELECT 
        s.id, s.name,
        c.id AS course_id, c.name AS course_name
    FROM student s
    LEFT JOIN student_course sc ON s.id = sc.student_id
    LEFT JOIN course c ON sc.course_id = c.id
    WHERE s.id = #{id}
</select>

核心说明

五、关联查询的优化与最佳实践

5.1 避免N+1查询问题

N+1问题:嵌套查询时,若查询N个主表记录,会触发1次主表查询+N次关联表查询,导致性能下降。

示例:查询所有用户及其订单(嵌套查询方式):

<!-- 1次主表查询:查询所有用户 -->
<select id="selectAllUser" resultMap="UserWithOrdersNestedMap">
    SELECT id, username, age FROM user
</select>

<!-- 每个用户触发1次订单查询(若有100个用户,触发100次) -->
<collection property="orders" select="selectOrdersByUserId" column="id"/>

解决方案

5.2 合理使用别名避免字段冲突

多表查询时,若主表与关联表有同名字段(如idname),需通过别名区分,否则映射结果会被覆盖。

-- 错误:未用别名,o.id会覆盖u.id
SELECT u.id, u.name, o.id, o.name 
FROM user u JOIN order o ON u.id = o.user_id

-- 正确:用别名区分
SELECT 
    u.id AS user_id, u.name AS user_name,
    o.id AS order_id, o.name AS order_name

5.3 按需查询关联数据

并非所有场景都需要查询关联数据(如列表页展示用户基本信息,无需查询订单),应根据场景设计不同查询:

5.4 延迟加载(按需加载关联数据)

MyBatis支持延迟加载(懒加载):查询主表数据时不加载关联数据,仅当访问关联属性时才触发关联查询,适合“大部分场景不需要关联数据”的场景。

开启延迟加载(在MyBatis配置文件中):

<settings>
    <setting name="lazyLoadingEnabled" value="true"/> <!-- 全局开启延迟加载 -->
    <setting name="aggressiveLazyLoading" value="false"/> <!-- 按需加载(访问时才加载) -->
</settings>

使用场景:详情页默认展示用户信息,点击“查看订单”按钮才加载订单数据(通过代码触发关联属性访问)。

六、常见问题与避坑指南

6.1 关联对象为null(映射失败)

问题:关联对象(如idCard)为null,但数据库存在关联数据。

原因

解决方案

6.2 集合数据重复(一条数据被多次映射)

问题collection映射的列表中,同一条数据被重复添加(如一个订单出现多次)。

原因

解决方案

<collection property="orders" ofType="Order">
    <id column="order_id" property="id"/> <!-- 关键:指定订单唯一标识 -->
    <!-- 其他字段 -->
</collection>

6.3 嵌套查询参数传递错误

问题:嵌套查询时,column传递的参数不正确,导致关联查询无结果。

解决方案

<!-- 主查询返回字段为user_id -->
<select id="selectUser" resultMap="UserMap">
    SELECT id AS user_id, username FROM user
</select>

<!-- 嵌套查询需用主查询的字段名作为参数 -->
<association select="selectOrder" column="user_id"/> <!-- 正确:column="user_id" -->
<association select="selectByParams" column="{id=user_id, name=user_name}"/>

总结:关联查询的核心要点
MyBatis关联查询通过resultMapassociationcollection标签,实现了多表数据到Java对象关联关系的映射,核心要点如下:

  1. 标签选择
  1. 查询方式
  1. 优化技巧

到此这篇关于MyBatis的关联查询实现(一对一、一对多、多对多)的文章就介绍到这了,更多相关MyBatis 关联查询内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文