当前位置:首页 > 技术 > 正文内容

MyBatis 关联映射实战:深入解析多对一与一对多查询

访客 技术 2026年8月10日 1

在掌握基础的结果映射机制后,实际业务开发中往往面临复杂的数据结构,即数据分散在多个关联表中。MyBatis 通过 <association><collection> 标签,能够优雅地处理实体对象之间的多对一(一对一)、一对多及多对多关联关系。

数据库模型设计

为演示多表关联,我们基于 user(用户)与 account(账户)两张表构建示例环境。在此场景中,一个用户可以持有多个账户,而每个账户归属于唯一的一个用户。

1. 建表脚本

请在目标数据库中执行以下 SQL 语句创建数据结构:

USE mybatis_db;

-- 清理旧数据
DROP TABLE IF EXISTS `account`;
DROP TABLE IF EXISTS `user`;

-- 1. 构建用户主表
CREATE TABLE `user` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `username` VARCHAR(32) NOT NULL COMMENT '用户名',
  `birthday` DATETIME DEFAULT NULL COMMENT '生日',
  `sex` CHAR(1) DEFAULT NULL COMMENT '性别',
  `address` VARCHAR(256) DEFAULT NULL COMMENT '地址',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- 插入测试用户数据
INSERT INTO `user`(`id`, `username`, `birthday`, `sex`, `address`) 
VALUES 
(1, '张三', '2023-01-01 10:00:00', '男', '北京'),
(2, '李四', '2023-02-02 11:00:00', '女', '上海'),
(3, '王五', '2023-03-03 12:00:00', '女', '深圳');

-- 2. 构建账户子表(含外键)
CREATE TABLE `account` (
  `ID` INT(11) NOT NULL COMMENT '编号',
  `UID` INT(11) DEFAULT NULL COMMENT '所属用户 ID',
  `AMOUNT` DOUBLE DEFAULT NULL COMMENT '余额',
  PRIMARY KEY (`ID`),
  CONSTRAINT `fk_account_user` FOREIGN KEY (`UID`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- 插入测试账户数据
INSERT INTO `account`(`ID`, `UID`, `AMOUNT`) 
VALUES 
(1, 1, 5000.00),
(2, 1, 3000.00),
(3, 2, 8000.00);

数据录入完成后,表结构大致如下所示(示意):

2. 关联关系定义

  • 多对一 (Many-to-One)
    • 场景:从账户视角出发,查找其关联的用户。
    • 实现:使用 <association> 标签。
    • 代码体现:在 Account 类中包含 User 属性。
  • 一对多 (One-to-Many)
    • 场景:从用户视角出发,一次性获取该用户的多个账户。
    • 实现:使用 <collection> 标签。
    • 代码体现:在 User 类中包含 List 集合。

项目环境配置

创建一个标准的 Maven 项目,命名为 MyBatis_Relation_Demo,并引入必要的依赖库。

<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.mybatis.demo</groupId>
    <artifactId>MyBatis_Relation_Demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- 数据库驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>

        <!-- MyBatis 核心包 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.13</version>
        </dependency>

        <!-- 单元测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

场景一:多对一查询(Account → User)

需求:查询所有账户信息,并同步获取该账户关联的用户名称。

1. 实体类设计

修改 Account 对象,增加 User 成员变量以承接关联数据。

public class Account {
    private Integer id;
    private Integer uid;
    private Double amount;

    // 多对一关联:单个 User 对象
    private User user; 

    // Getter & Setter 省略...
}

2. Mapper 接口与 XML 映射

AccountMapper.xml 中利用 resultMap 结合 <association> 完成映射。

<mapper namespace="com.mybatis.demo.mapper.AccountMapper">
    
    <!-- 自定义结果映射:处理多对一 -->
    <resultMap id="accUserMap" type="com.mybatis.demo.model.Account">
        <!-- 账户自身字段映射 -->
        <id property="id" column="id"/>
        <result property="uid" column="uid"/>
        <result property="amount" column="amount"/>

        <!-- 关联用户对象 -->
        <association property="user" javaType="com.mybatis.demo.model.User">
            <result property="username" column="username"/>
            <result property="address" column="address"/>
        </association>
    </resultMap>

    <!-- 执行 SQL:隐式内连接 -->
    <select id="findAllAccWithUser" resultMap="accUserMap">
        SELECT a.id, a.uid, a.amount, u.username, u.address 
        FROM account a
        JOIN user u ON a.uid = u.id
    </select>
</mapper>

3. 测试验证

运行测试代码,控制台输出应包含嵌套的 User 对象信息:

@Test
public void testAccQuery() throws IOException {
    SqlSession session = MyUtil.getSession();
    AccountMapper mapper = session.getMapper(AccountMapper.class);
    List<Account> list = mapper.findAllAccWithUser();
    list.forEach(System.out::println);
    session.close();
}

执行截图效果如下:

场景二:一对多查询(User → Accounts)

需求:查询所有用户,并列出该用户下所有的账户列表。

1. 实体类改造

此时需要在 User 类中添加集合属性:

public class User {
    private Integer id;
    // ... 其他字段

    // 一对多关联:账户集合
    private List<Account> accounts; 

    // Getters & Setters
}

2. Mapper XML 配置

关键在于使用 <collection> 标签来接收集合数据。注意处理列别名以避免主键冲突(当 User 和 Account 有相同 ID 时)。

<mapper namespace="com.mybatis.demo.mapper.UserMapper">

    <resultMap id="usrAccMap" type="com.mybatis.demo.model.User">
        <!-- 主表映射 -->
        <id property="id" column="uid"/>
        <result property="username" column="uname"/>
        
        <!-- 集合映射 -->
        <collection property="accounts" ofType="com.mybatis.demo.model.Account">
            <id property="id" column="aid"/>
            <result property="amount" column="amnt"/>
        </collection>
    </resultMap>

    <select id="findAllUserWithAccs" resultMap="usrAccMap">
        -- 左连接以确保无账户的用户也能查出
        SELECT u.id as uid, u.username as uname, 
               a.id as aid, a.amount as amnt 
        FROM user u
        LEFT JOIN account a ON u.id = a.uid
    </select>
</mapper>

3. 测试与结果

调用 UserMapper 的查询方法,结果将打印出带有嵌套账户列表的用户信息。

@Test
public void testUserQuery() throws Exception {
    SqlSession session = MyUtil.getSession();
    UserMapper mapper = session.getMapper(UserMapper.class);
    List<User> users = mapper.findAllUserWithAccs();
    users.forEach(u -> System.out.println(u.getUsername() + " | " + u.getAccounts()));
    session.close();
}

控制台输出示例显示列表数据已被正确封装:

核心差异总结

两种查询模式均依赖于 resultMap 的配置,主要区别在于关联属性的类型与对应的标签选择:

对比维度 多对一 (Many-to-One) 一对多 (One-to-Many)
核心标签 <association> <collection>
属性类型 单一对象 (Object) 泛型集合 (List<Type>)
指定类型方式 javaType ofType
SQL 方向 通常查子表带父表 通常查父表带子表

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。