MyBatis 一对一查询中的 `<association>` 标签配置详解
MyBatis 一对一查询中的 <association>
标签配置详解
引言
在使用 MyBatis 进行数据库操作时,一对一查询是一种常见的需求。尤其是在处理主表和从表之间的关联关系时,如何正确配置 <association>
标签成为了一个关键问题。本文将通过一个具体的案例,详细分析 MyBatis 中 <association>
标签的配置方式,并解决在实际开发中遇到的用户编号不一致的问题。
1. 案例背景
我们有两个表:
Order
表(主表):存储订单信息,包含字段id
(订单ID)、user_id
(用户ID)、order_number
(订单编号)。User
表(从表):存储用户信息,包含字段id
(用户ID)、user_name
(用户名)、password
(密码)、name
(姓名)、age
(年龄)、sex
(性别)。
表关系如下
需求是通过订单编号查询订单信息,并同时查询出下单用户的信息。
2. 实体类定义
-
Order
实体类:public class Order { private Integer id; // 订单ID private Long userID; // 用户ID private String orderNumber; // 订单编号 private User user; // 关联的用户对象 // 省略 getter 和 setter 方法 }
-
User
实体类:public class User { private Long id; // 用户ID private String userName; // 用户名 private String passWord; // 密码 private String name; // 姓名 private Integer age; // 年龄 private Integer sex; // 性别 // 省略 getter 和 setter 方法 }
3. SQL 查询
为了实现一对一查询,我们使用 JOIN
语句将 Order
表和 User
表关联起来:
SELECT
o.id AS order_id,
o.user_id AS order_user_id,
o.order_number AS order_number,
u.id AS user_id,
u.user_name AS user_name,
u.password AS password,
u.name AS name,
u.age AS age,
u.sex AS sex
FROM
tb_order o
INNER JOIN
tb_user u
ON
o.user_id = u.id
WHERE
o.order_number = #{orderNumber}
4. MyBatis 映射文件
在 MyBatis 中,使用 <resultMap>
和 <association>
标签来配置一对一映射关系。<association>
标签里面 写的是添加从表中字段与实体属性映射关系
(1)错误配置
以下是错误的 <resultMap>
配置:
<resultMap id="findOrderByOrderNumberResultMap" type="Order" autoMapping="true">
<!-- 主表(Order)的映射 -->
<id column="order_id" property="id"/> <!-- Order 表的主键 -->
<result column="user_id" property="userID"/> <!-- Order 表的外键 -->
<result column="order_number" property="orderNumber"/> <!-- Order 表的其他字段 -->
<!-- 从表(User)的映射 -->
<association property="user" javaType="User" autoMapping="true">
<id column="id" property="id"/> <!-- 错误的配置:将 Order 表的 id 列映射到 User 实体的 id 属性 -->
<result column="user_name" property="userName"/>
<result column="password" property="passWord"/>
<result column="name" property="name"/>
<result column="age" property="age"/>
<result column="sex" property="sex"/>
</association>
</resultMap>
(2)错误原因分析
column="id"
:这里将Order
表中的id
列映射到User
实体类中的id
属性。- 问题:
Order
表中的id
是订单的主键,而User
表中的id
是用户的主键。这两个字段没有直接关系,因此会导致映射错误。 - 结果:
User
实体类中的id
属性被错误地赋值为Order
表中的id
,而不是User
表中的id
。
(3)正确配置
以下是正确的 <resultMap>
配置:
<resultMap id="findOrderByOrderNumberResultMap" type="Order" autoMapping="true">
<!-- 主表(Order)的映射 -->
<id column="order_id" property="id"/> <!-- Order 表的主键 -->
<result column="user_id" property="userID"/> <!-- Order 表的外键 -->
<result column="order_number" property="orderNumber"/> <!-- Order 表的其他字段 -->
<!-- 从表(User)的映射 -->
<association property="user" javaType="User" autoMapping="true">
<id column="user_id" property="id"/> <!-- 正确的配置:将 User 表的 id 列映射到 User 实体的 id 属性 -->
<result column="user_name" property="userName"/>
<result column="password" property="passWord"/>
<result column="name" property="name"/>
<result column="age" property="age"/>
<result column="sex" property="sex"/>
</association>
</resultMap>
(4)正确配置分析
column="user_id"
:这里将Order
表中的user_id
列映射到User
实体类中的id
属性。- 原因:
Order
表中的user_id
是外键,指向User
表中的id
。通过这个外键字段,MyBatis 可以正确查询到User
表中的对应记录。 - 结果:
User
实体类中的id
属性被正确地赋值为User
表中的id
。
5. 测试与结果
(1)错误配置的测试结果
通过错误配置查询订单编号为 20140921003
的订单信息,并输出结果:
@Test
public void testFindOrderByOrderNumber() {
Order order = orderMapper.findOrderByOrderNumber("20140921003");
System.out.println(order);
}
输出结果:
Order{id=3, userID=1, orderNumber='20140921003', user=User{id=3, userName='zhangsan', passWord='123456', name='张三', age=30, sex=1}}
- 问题:
User
实体类中的id
属性被错误地赋值为Order
表中的id
(3
),而不是User
表中的id
(1
)。
(2)正确配置的测试结果
通过正确配置查询订单编号为 20140921003
的订单信息,并输出结果:
@Test
public void testFindOrderByOrderNumber() {
Order order = orderMapper.findOrderByOrderNumber("20140921003");
System.out.println(order);
}
输出结果:
Order{id=3, userID=1, orderNumber='20140921003', user=User{id=1, userName='zhangsan', passWord='123456', name='张三', age=30, sex=1}}
- 结果:
User
实体类中的id
属性被正确地赋值为User
表中的id
(1
)。
6. 核心问题
在 MyBatis 的一对一查询中,<association>
标签的配置是关键。尤其是 column
和 property
的配置,直接影响查询结果的正确性。
column
:应该填写 主表的外键字段(例如tb_order
表中的user_id
),而不是从表的主键字段(例如tb_user
表中的id
)。property
:应该填写 从表实体类中的属性名(例如User
实体类中的id
)。
7. 错误配置分析
以下是一个错误的配置示例:
<association property="user" javaType="User">
<id column="id" property="id"/>
</association>
- 错误原因:
column="id"
:这里将Order
表中的id
列映射到User
实体类中的id
属性。- 问题:
Order
表中的id
是订单的主键,而User
表中的id
是用户的主键。这两个字段没有直接关系,因此会导致映射错误。 - 结果:
User
实体类中的id
属性被错误地赋值为Order
表中的id
,而不是User
表中的id
。
8. 正确配置分析
以下是正确的配置示例:
<association property="user" javaType="User">
<id column="user_id" property="id"/>
</association>
- 正确原因:
column="user_id"
:这里将Order
表中的user_id
列映射到User
实体类中的id
属性。- 原因:
Order
表中的user_id
是外键,指向User
表中的id
。通过这个外键字段,MyBatis 可以正确查询到User
表中的对应记录。 - 结果:
User
实体类中的id
属性被正确地赋值为User
表中的id
。
9. 映射规则总结
- 主表的外键字段 (
column
):用来指示主表和从表的关联。一般来说,主表中的外键字段会指向从表中的主键字段。 - 从表的主键字段(通常是
id
):在 POJO 类中对应的属性(property
)会映射到主表查询结果的某个字段。
10. 为什么 column="user_id"
是正确的?
- 主表外键字段:在
tb_order
表中,user_id
是外键,它指向tb_user
表的id
字段。为了通过tb_order
表来查询相关的User
信息,必须通过user_id
来建立和tb_user
表的关系。 - 映射关系:在
<association>
标签中,column="user_id"
就是将主表的外键字段user_id
与从表(tb_user
)的主键字段id
关联起来,然后 MyBatis 会使用这个外键字段去查询从表tb_user
中的对应记录。
11. 错误场景分析
如果像这样设置:
<association property="user" javaType="User">
<id column="id" property="id"/>
</association>
column="id"
:这时 MyBatis 会将tb_order
表中的id
字段(订单的主键)与从表tb_user
表中的id
字段进行匹配。显然,这个逻辑是错的,因为tb_order
表中的id
字段和tb_user
表的id
字段没有直接关系,tb_order
的id
只是订单的主键,而user_id
才是关联用户表的外键字段。
12.怎么理解<association>
标签的主要作用是配置主表实体类与从表实体类之间的关系
在这个标签中,我们通过指定从表的字段与实体类的属性之间的映射关系,将从表的查询结果正确地注入到主表实体类中。
简单来说,<association>
标签的功能是告诉 MyBatis:从表的查询结果要如何映射到主表实体类中的某个属性。
核心概念理解
-
主表和从表的关系:
- 主表(如
Order
):在查询中是主要表,实体类中包含一个从表对象(如User
)。 - 从表(如
User
):作为关联表,其字段会被映射到主表实体类中对应的属性。
- 主表(如
-
property
:- 表示主表实体类中的属性名(主表实体类中的一个字段)。
- 这个属性通常是从表实体类的对象,比如
Order
中的User user
。
-
column
:- 表示从表的字段或与主表的外键字段相关联的字段。
-
<association>
的具体作用:- 把从表的字段值(数据库的列值)映射到主表实体类中对应的属性(
property
)。 - 通过
column
指定主表外键字段,将其用于从表的查询。
- 把从表的字段值(数据库的列值)映射到主表实体类中对应的属性(
关键配置
在 <association>
标签内部,配置从表的字段与从表实体类属性的对应关系。具体包括以下两点:
1. 从表字段映射
通过 <result>
和 <id>
标签,定义从表字段到从表实体属性的映射,例如:
<association property="user" javaType="User">
<id column="user_id" property="id"/> <!-- 主表的 user_id 外键映射到从表 User 的 id 属性 -->
<result column="user_name" property="userName"/> <!-- 从表的 user_name 映射到 User 类的 userName 属性 -->
<result column="password" property="passWord"/> <!-- 从表的 password 映射到 User 类的 passWord 属性 -->
<result column="name" property="name"/> <!-- 从表的 name 映射到 User 类的 name 属性 -->
<result column="age" property="age"/> <!-- 从表的 age 映射到 User 类的 age 属性 -->
<result column="sex" property="sex"/> <!-- 从表的 sex 映射到 User 类的 sex 属性 -->
</association>
2. 主表外键与从表主键的映射
column
中填写主表的外键字段,property
对应的是从表实体类的主键字段。例如:
<id column="user_id" property="id"/>
column="user_id"
:主表Order
的外键字段user_id
。property="id"
:从表实体类User
中的主键字段id
。
MyBatis 会利用 user_id
去从表中查询对应的用户信息,然后将结果注入到主表实体类的 user
属性中。
如何理解“添加从表字段与实体属性的映射关系”
在 <association>
中添加从表字段与实体属性的映射关系,简单来说就是以下过程:
-
从表字段和实体属性的映射:
- 数据库中的从表字段(列名)会被映射到从表实体类的某个属性上。
- 例如:
- 数据库字段
user_name
-> 从表实体类User
的userName
属性。 - 数据库字段
password
-> 从表实体类User
的passWord
属性。
- 数据库字段
-
从表实体映射到主表属性:
- 主表实体类的某个属性(如
Order
的User user
)需要映射从表的整个实体类。 - MyBatis 会通过主表的外键字段(如
user_id
)与从表的主键字段(如id
)建立关联,将从表的实体对象注入到主表。
- 主表实体类的某个属性(如
简单类比:拼图原理
把 <association>
标签理解为拼图工具:
- 数据库中的从表字段是拼图碎片。
- 从表实体类的属性是拼图目标。
<association>
的配置就是在定义:哪些碎片对应拼成目标中的哪个部分。
最终,这些“拼好”的从表数据会被作为一个完整对象(从表实体类)注入到主表实体类中。
13 总结
-
<association>
标签内部的字段映射(从表字段 -> 从表实体类属性)用于:- 把从表的查询结果映射到从表实体类的属性中。
- 再将从表实体类整体注入到主表实体类的某个属性中。
-
配置关键:
- 明确主表外键字段和从表主键字段的对应关系(
column
和property
)。 - 正确配置从表字段与从表实体类属性的映射关系。
- 明确主表外键字段和从表主键字段的对应关系(
通过正确的配置,可以实现主表与从表实体类之间的数据注入,满足实际开发中的一对一查询需求。
-
column
:应该填写 主表的外键字段(例如tb_order
表中的user_id
)。 -
property
:应该填写 从表实体类中的属性名(例如User
实体类中的id
)。 -
映射逻辑:MyBatis 会通过主表的外键字段(
user_id
)去查询从表(tb_user
)中的对应记录,并将结果映射到从表实体类中。 -
column
和property
的区别:column
是数据库表中的列名。property
是实体类中的属性名。
-
<association>
标签的作用:用于配置主表实体与从表实体之间的一对一关系。 -
<id column="user_id" property="id"/>
的含义:将查询结果集中的user_id
列映射到User
实体类中的id
属性。 -
数据一致性:确保数据库中的外键(
Order.user_id
)与主键(User.id
)一致,避免映射错误。
通过以上步骤,我们成功实现了一对一查询,并解决了映射中的疑问。希望这篇博客能帮助你更好地理解 MyBatis 的一对一查询机制,并在实际开发中避免类似的问题。
作者:周政然
日期:2024年1月4日
版权声明:本文为原创文章,转载请注明出处。
希望这篇博客对你有所帮助!如果还有疑问,欢迎继续讨论!