方法一
User 类:
public class User {
private int userId;
private String userName;
private List<Video> videoList; // 存储与用户相关的多个视频
// getters and setters
}
Video 类:
public class Video {
private int videoId;
private String videoName;
// getters and setters
}
是的,使用 JOIN
查询可以实现一对多的映射,但需要一些额外的配置来处理如何将查询结果映射到目标对象中。具体来说,查询结果将返回多个相同主对象的数据行,每一行包含主对象和子对象(关联数据)。通过 MyBatis 的 resultMap
配置,你可以将这些结果正确地映射到一对多的 Java 对象结构中。
一对多关系的 JOIN 查询与映射
假设我们有两个表:
- 用户表(users):存储用户信息
- 视频表(videos):存储每个视频的信息,每个视频关联到一个用户(
user_id
)。
我们希望查询所有用户及其相关的视频列表。因为是“一对多”关系,每个用户可能有多个视频,所以查询结果将包含多行数据(每个视频对应一行),但我们希望这些视频数据被正确地映射到每个用户的 videoList
属性中。
1. SQL 查询:
我们首先需要编写一个 SQL 查询,使用 JOIN
来获取每个用户及其相关的视频:
SELECT
u.id AS user_id,
u.name AS user_name,
v.id AS video_id,
v.name AS video_name,
v.video_cover
FROM
users u
LEFT JOIN
videos v ON u.id = v.user_id
ORDER BY
u.id, v.id;
这个查询将会返回多个记录:
- 每个用户有多个视频时,将返回多行,包含相同的
user_id
和不同的video_id
。
2. 结果映射(resultMap
)配置:
在 MyBatis 中,我们可以通过 resultMap
和 collection
元素将查询结果映射到一对多关系的 Java 对象中。
配置一对多映射:
<resultMap id="userWithVideos" type="User">
<!-- 映射用户属性 -->
<id property="id" column="user_id"/>
<result property="name" column="user_name"/>
<!-- 使用 collection 来映射一对多关系 -->
<collection property="videoList" ofType="Video">
<id property="id" column="video_id"/>
<result property="name" column="video_name"/>
<result property="videoCover" column="video_cover"/>
</collection>
</resultMap>
<!-- 查询用户及其视频 -->
<select id="selectUsersWithVideos" resultMap="userWithVideos">
SELECT
u.id AS user_id,
u.name AS user_name,
v.id AS video_id,
v.name AS video_name,
v.video_cover
FROM
users u
LEFT JOIN
videos v ON u.id = v.user_id
ORDER BY
u.id, v.id
</select>
解释:
-
主表的映射:
- 在
resultMap
中,<id>
元素将user_id
映射到User
类的id
属性,<result>
元素将其他字段(如name
)映射到User
类的相应属性。
- 在
-
一对多关系的映射:
<collection>
元素用于表示一对多关系。property="videoList"
将结果中的视频数据映射到User
对象的videoList
属性(即视频列表)。ofType="Video"
表示每个视频将映射到Video
类的一个对象。- 通过
id
和result
子元素将视频表中的字段(如video_id
,video_name
,video_cover
)映射到Video
类的对应属性。
3. 查询结果:
假设查询返回如下结果:
user_id | user_name | video_id | video_name | video_cover |
---|---|---|---|---|
1 | Alice | 1 | Video 1 | cover1.jpg |
1 | Alice | 2 | Video 2 | cover2.jpg |
2 | Bob | 3 | Video 3 | cover3.jpg |
3 | Charlie | NULL | NULL | NULL |
- Alice 有两个视频(Video 1 和 Video 2)。
- Bob 有一个视频(Video 3)。
- Charlie 没有视频。
4. 映射到 Java 对象:
最终,查询结果会被映射到 Java 对象中,User
对象的 videoList
属性将包含对应用户的所有视频。
假设我们有如下的 Java 类:
public class User {
private int id;
private String name;
private List<Video> videoList; // 一对多关系,存储用户的视频列表
// getters and setters
}
public class Video {
private int id;
private String name;
private String videoCover;
// getters and setters
}
Alice
对应的User
对象的videoList
属性会是一个包含Video 1
和Video 2
的列表。Bob
对应的User
对象的videoList
属性会是一个包含Video 3
的列表。Charlie
对应的User
对象的videoList
属性会是null
或者一个空列表(如果没有视频)。
5. 最终结果:
假设我们调用 selectUsersWithVideos()
方法,返回的结果将是:
List<User> users = userMapper.selectUsersWithVideos();
// 对于每个用户
for (User user : users) {
System.out.println(user.getName()); // 打印用户名字
for (Video video : user.getVideoList()) {
System.out.println(video.getName()); // 打印用户的视频名字
}
}
users
中会包含所有用户的信息(包括没有视频的用户)。- 每个
User
对象的videoList
属性将包含该用户的视频信息。- 对于 Alice,
videoList
会包含Video 1
和Video 2
。 - 对于 Bob,
videoList
会包含Video 3
。 - 对于 Charlie,
videoList
会为空或null
。
- 对于 Alice,
总结:
使用 JOIN
查询来避免 N+1 查询问题时,结合 MyBatis
中的 resultMap
和 collection
元素,我们能够将查询结果正确地映射到一对多的 Java 对象中。这样,即使查询结果包含多行数据(例如每个用户和对应的多个视频),也能通过适当的映射将它们正确地组织到一个主对象的属性(如 videoList
)中。这不仅解决了 N+1 查询问题,还保证了数据在 Java 对象中的结构清晰和一致。
方法二
使用第三种方式,即 ResultMap
+ select
,来实现一对多映射,我们将通过主查询获取用户信息,然后通过子查询查询与每个用户相关的视频信息。下面是如何实现的步骤。
场景背景
我们有两个表:
users
表:存储用户信息。videos
表:存储视频信息,每个视频与一个用户相关。
数据库表结构假设
users
表(用户表):
user_id | user_name |
---|---|
1 | Alice |
2 | Bob |
videos
表(视频表):
video_id | video_name | user_id |
---|---|---|
1 | Video 1 | 1 |
2 | Video 2 | 1 |
3 | Video 3 | 2 |
Java 类定义
User 类
public class User {
private int userId;
private String userName;
private List<Video> videoList; // 存储与用户相关的多个视频
// getters and setters
}
Video 类
public class Video {
private int videoId;
private String videoName;
// getters and setters
}
MyBatis 映射配置
1. 结果映射配置 (resultMap
)
我们使用 ResultMap
来指定如何映射用户及其相关的视频信息。
<resultMap id="userWithVideos" type="User">
<!-- 映射用户的基本信息 -->
<id property="userId" column="user_id"/>
<result property="userName" column="user_name"/>
<!-- 映射视频列表 -->
<collection property="videoList" column="user_id" select="com.easylive.mappers.UserMapper.selectVideosByUserId"/>
</resultMap>
property="videoList"
:指向User
类中的videoList
属性。column="user_id"
:用user_id
列来关联主查询和子查询。select="com.easylive.mappers.UserMapper.selectVideosByUserId"
:指定子查询的方法,MyBatis 会根据user_id
执行selectVideosByUserId
查询来填充videoList
。
2. 子查询 (selectVideosByUserId
)
子查询用于查询与用户相关的所有视频信息。此查询会根据 user_id
过滤出该用户的所有视频。
<select id="selectVideosByUserId" resultType="Video">
SELECT
v.video_id,
v.video_name
FROM
videos v
WHERE
v.user_id = #{userId}
ORDER BY
v.video_id
</select>
resultType="Video"
:子查询返回的结果将映射为Video
类的对象。#{userId}
:传入的userId
参数来自主查询,它会用每个User
的user_id
来执行子查询,填充videoList
。
3. 主查询 (selectListWithVideos
)
主查询用于获取所有用户信息。它将返回 users
表中的所有用户数据,然后通过子查询填充 videoList
。
<select id="selectListWithVideos" resultMap="userWithVideos">
SELECT
u.user_id,
u.user_name
FROM
users u
<if test="query.orderBy != null">
ORDER BY ${query.orderBy}
</if>
<if test="query.simplePage != null">
LIMIT #{query.simplePage.start}, #{query.simplePage.end}
</if>
</select>
resultMap="userWithVideos"
:使用先前定义的resultMap
,将查询结果映射为User
对象,并填充videoList
。query.simplePage
和query.orderBy
:如果有分页或排序条件,这里可以控制查询的分页和排序。
Java 代码调用
假设 UserMapper
中已经定义了查询方法,你可以像下面这样调用查询:
List<User> users = userMapper.selectListWithVideos(query);
for (User user : users) {
System.out.println("User: " + user.getUserName());
for (Video video : user.getVideoList()) {
System.out.println(" Video: " + video.getVideoName());
}
}
执行流程
- 主查询:执行
selectListWithVideos
查询,获取所有users
表中的用户信息。 - 子查询:每个
user
根据其user_id
执行selectVideosByUserId
查询,获取该用户的所有视频。 - 映射结果:MyBatis 将查询的结果映射为
User
对象,并将每个用户的相关视频填充到videoList
属性中。 - 返回结果:最终返回的
users
列表包含每个用户以及其相关的多个视频。
查询示例结果
假设查询返回了以下数据:
user_id | user_name | video_id | video_name |
---|---|---|---|
1 | Alice | 1 | Video 1 |
1 | Alice | 2 | Video 2 |
2 | Bob | 3 | Video 3 |
最终返回的 Java 对象是:
[
{
"userId": 1,
"userName": "Alice",
"videoList": [
{"videoId": 1, "videoName": "Video 1"},
{"videoId": 2, "videoName": "Video 2"}
]
},
{
"userId": 2,
"userName": "Bob",
"videoList": [
{"videoId": 3, "videoName": "Video 3"}
]
}
]
总结
使用 ResultMap
+ select
方式来实现一对多映射的步骤如下:
- 主查询通过
select
查询主表数据(用户信息)。 - 子查询根据主查询中的
user_id
查询相关的子数据(视频信息)。 ResultMap
映射用户及其视频列表,通过<collection>
填充一对多关系。
这种方式简洁易懂,可以灵活地分离查询逻辑,同时适用于数据量不大的场景。如果需要处理复杂的查询或子查询,使用这种方式能够很好地组织代码并控制查询粒度。