MyBatis中一对多结果映射

方法一

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 查询与映射

假设我们有两个表:

  1. 用户表(users):存储用户信息
  2. 视频表(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 中,我们可以通过 resultMapcollection 元素将查询结果映射到一对多关系的 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>

解释:

  1. 主表的映射

    • resultMap 中,<id> 元素将 user_id 映射到 User 类的 id 属性,<result> 元素将其他字段(如 name)映射到 User 类的相应属性。
  2. 一对多关系的映射

    • <collection> 元素用于表示一对多关系。property="videoList" 将结果中的视频数据映射到 User 对象的 videoList 属性(即视频列表)。
    • ofType="Video" 表示每个视频将映射到 Video 类的一个对象。
    • 通过 idresult 子元素将视频表中的字段(如 video_id, video_name, video_cover)映射到 Video 类的对应属性。

3. 查询结果:

假设查询返回如下结果:

user_iduser_namevideo_idvideo_namevideo_cover
1Alice1Video 1cover1.jpg
1Alice2Video 2cover2.jpg
2Bob3Video 3cover3.jpg
3CharlieNULLNULLNULL
  • 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 1Video 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 属性将包含该用户的视频信息。
    • 对于 AlicevideoList 会包含 Video 1Video 2
    • 对于 BobvideoList 会包含 Video 3
    • 对于 CharlievideoList 会为空或 null

总结:

使用 JOIN 查询来避免 N+1 查询问题时,结合 MyBatis 中的 resultMapcollection 元素,我们能够将查询结果正确地映射到一对多的 Java 对象中。这样,即使查询结果包含多行数据(例如每个用户和对应的多个视频),也能通过适当的映射将它们正确地组织到一个主对象的属性(如 videoList)中。这不仅解决了 N+1 查询问题,还保证了数据在 Java 对象中的结构清晰和一致。

方法二

使用第三种方式,即 ResultMap + select,来实现一对多映射,我们将通过主查询获取用户信息,然后通过子查询查询与每个用户相关的视频信息。下面是如何实现的步骤。

场景背景

我们有两个表:

  1. users 表:存储用户信息。
  2. videos 表:存储视频信息,每个视频与一个用户相关。

数据库表结构假设

users 表(用户表):
user_iduser_name
1Alice
2Bob
videos 表(视频表):
video_idvideo_nameuser_id
1Video 11
2Video 21
3Video 32

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 参数来自主查询,它会用每个 Useruser_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.simplePagequery.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());
    }
}

执行流程

  1. 主查询:执行 selectListWithVideos 查询,获取所有 users 表中的用户信息。
  2. 子查询:每个 user 根据其 user_id 执行 selectVideosByUserId 查询,获取该用户的所有视频。
  3. 映射结果:MyBatis 将查询的结果映射为 User 对象,并将每个用户的相关视频填充到 videoList 属性中。
  4. 返回结果:最终返回的 users 列表包含每个用户以及其相关的多个视频。

查询示例结果

假设查询返回了以下数据:

user_iduser_namevideo_idvideo_name
1Alice1Video 1
1Alice2Video 2
2Bob3Video 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 方式来实现一对多映射的步骤如下:

  1. 主查询通过 select 查询主表数据(用户信息)。
  2. 子查询根据主查询中的 user_id 查询相关的子数据(视频信息)。
  3. ResultMap 映射用户及其视频列表,通过 <collection> 填充一对多关系。

这种方式简洁易懂,可以灵活地分离查询逻辑,同时适用于数据量不大的场景。如果需要处理复杂的查询或子查询,使用这种方式能够很好地组织代码并控制查询粒度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值