当前位置: 首页 > news >正文

Mybatis第二章(中):多表查询核心实战之多对一查询和一对多查询(文章最后附详细可运行代码!!!)

我们在Mybatis第二章(上)简单讲述了resultMap 结果映射,接下来将深入 MyBatis 多表查询的核心场景。

在实际业务中,数据往往分布在多张表中,通过外键关联,MyBatis 提供了 association 和 collection 标签,优雅地解决了多对一(一对一)、一对多、多对多的关联查询问题。

一、数据库多表设计

在讲解多表查询之前,我们先搭建本次案例的数据库环境,核心是 user(用户)和 account(账户)两张表,模拟一个用户对应多个账户的场景:

1.1 建表与初始化数据

在mybatis_db数据库下面创建两张表,分别为user和account---这里user表我之前已经创建过,为了更好的展示,这里我把user表删掉,从头开始

USE mybatis_db;drop table user;-- 1. 创建用户表(如果之前没建,也可以直接用这段)
CREATE TABLE IF NOT EXISTS `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, '老王', '2018-02-27 17:47:08', '男', '北京'),
(2, '熊大', '2018-03-02 15:09:37', '女', '上海'),
(3, '熊二', '2018-03-04 11:34:34', '女', '深圳'),
(4, '光头强', '2018-03-04 12:04:06', '男', '广州');-- 2. 创建账户表(含外键约束,和user表关联)
CREATE TABLE `account` (`ID` int(11) NOT NULL COMMENT '编号',`UID` int(11) default NULL COMMENT '用户编号',`MONEY` double default NULL COMMENT '金额',PRIMARY KEY (`ID`),KEY `FK_Reference_8` (`UID`),CONSTRAINT `FK_Reference_8` FOREIGN KEY (`UID`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- 插入账户数据
INSERT INTO `account`(`ID`, `UID`, `MONEY`) 
VALUES 
(1, 1, 1000),
(2, 2, 1000),
(3, 2, 2000);
经过上面的代码之后,我们对创建的两个表进行查看,结果如下:

image

1.2 什么是多对一(Many-to-One)?

1.2.1 通俗理解

多个账户 对应 一个用户。在数据库关系中,这是通过外键来实现的。比如:账户表(account) 中有一个字段 UID 指向了 ** 用户表(user)** 的主键 ID。多个账户共享同一个用户的数据。

1.2.2 为什么要查?

假设我们要查询账户信息,但我们不仅想知道它有多少钱,还想知道这个账户是属于谁的(比如用户的名字、地址)。这就是多对一查询:从账户出发,去关联查出它所属的用户信息。

1.2.3 代码实体对应

  • 被查对象(多):Account(账户)

  • 关联对象(一):User(用户)

  • 实体类写法:在 Account 类中添加一个 User 对象属性,用于封装所属用户。

1.2.4 标签核心

<association>:专门用于处理多对一(或一对一)的关联关系。

1.3 什么是一对多(One-to-Many)?

1.3.1 通俗理解

一个用户 对应 多个账户。这是多对一的逆向关系。比如:一个用户可以拥有多个银行卡账户。

1.3.2 为什么要查?

假设我们要查询 用户信息,并且想一次性把 该用户拥有的所有账户 都查出来。这就是一对多查询:从用户出发,把该用户拥有的所有账户列表一起查出来。

1.3.3 代码实体对应

  • 被查对象(一):User(用户)

  • 关联对象(多):List(账户集合)

  • 实体类写法:在 User 类中添加一个 List 集合属性,用于存储所有账户。

1.3.4 标签核心

<collection>:专门用于处理一对多的关联关系,封装集合数据。

1.4 多对一 vs 一对多:核心区别对比

维度 多对一 (Many-to-One) 一对多 (One-to-Many)
业务方向 从账户查用户 从用户查账户
核心逻辑 多个账户 -> 一个用户 一个用户 -> 多个账户
实体类属性 Account 中包含 User 对象 User 中包含 List<Account> 集合
MyBatis 标签 <association> <collection>
查询结果 单个对象(封装用户信息) 集合对象(封装账户列表)

1.5 创建一个普通Maven项目

创建时不勾选任何模板,一直点击next,包名为com.qcby,项目名为MybatisLearning03。

创建完成之后,在pom.xml中添加mysql、mybatis、junit、log4j四个依赖,pom.xml的代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.qcby</groupId><artifactId>MybatisLearning03</artifactId><version>1.0-SNAPSHOT</version><dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.13</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.9</version></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency></dependencies></project>

加入依赖之后,打开Maven Projects查看依赖是否导入

image

创建如下目录结构的Maven项目

image

各部分的作用如下:

image

本次案例基于 用户 (User) 和 账户 (Account) 两张表:

  • 多对一:我们写 AccountMapper,查询所有账户,同时带出账户所属的用户信息。

  • 一对多:我们写 UserMapper,查询所有用户,同时带出该用户拥有的所有账户信息。

二、多对一查询(一对一):账户关联所属用户

2.1 需求说明

查询所有账户信息,同时获取每个账户所属用户的名称和地址,最终返回包含用户信息的账户对象列表。

2.2 实体类文件

2.2.1 User.java(用户实体类)

package com.qcby.mybatis01.model;import java.util.Date;
import java.io.Serializable;public class User implements Serializable {private Integer id;private String username;private Date birthday;private String sex;private String address;// Getter/Setterpublic Integer getId() { return id; }public void setId(Integer id) { this.id = id; }public String getUsername() { return username; }public void setUsername(String username) { this.username = username; }public Date getBirthday() { return birthday; }public void setBirthday(Date birthday) { this.birthday = birthday; }public String getSex() { return sex; }public void setSex(String sex) { this.sex = sex; }public String getAddress() { return address; }public void setAddress(String address) { this.address = address; }@Overridepublic String toString() {return "User{" +"id=" + id +", username='" + username + '\'' +", birthday=" + birthday +", sex='" + sex + '\'' +", address='" + address + '\'' +'}';}
}

2.2.2 Account.java(账户实体,添加 User 属性)

package com.qcby.mybatis01.model;import java.io.Serializable;public class Account implements Serializable {private Integer id;private Integer uid;private Double money;// 新增:所属用户对象(多对一关联)private User user;// Getter/Setterpublic Integer getId() { return id; }public void setId(Integer id) { this.id = id; }public Integer getUid() { return uid; }public void setUid(Integer uid) { this.uid = uid; }public Double getMoney() { return money; }public void setMoney(Double money) { this.money = money; }public User getUser() { return user; }public void setUser(User user) { this.user = user; }@Overridepublic String toString() {return "Account{" +"id=" + id +", uid=" + uid +", money=" + money +", user=" + user +'}';}
}

2.3 Mapper 接口与 XML 配置

2.3.1 AccountMapper.java 接口

package com.qcby.mybatis01.mapper;import com.qcby.mybatis01.model.Account;
import java.util.List;public interface AccountMapper {// 查询所有账户,同时关联所属用户List<Account> findAll();
}

2.3.2 AccountMapper.xml 配置文件(核心:association 标签)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qcby.mybatis01.mapper.AccountMapper"><!-- 多对一查询:查询账户及所属用户 --><select id="findAll" resultMap="accountMap">select a.*, u.username, u.address from account a, user u where a.uid = u.id</select><!-- 自定义resultMap:处理多对一关联映射 --><resultMap id="accountMap" type="com.qcby.mybatis01.model.Account"><!-- 账户基础字段映射 --><result property="id" column="id"/><result property="uid" column="uid"/><result property="money" column="money"/><!-- 多对一关联:映射user对象 --><association property="user" javaType="com.qcby.mybatis01.model.User"><result property="username" column="username"/><result property="address" column="address"/></association></resultMap></mapper>

2.3.3 UserMapper.xml 配置文件(上面的目录结构中忘记创建了,我们在mappers文件夹下创建一个UserMapper.xml文件)

image

UserMapper.xml内容:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.qcby.mybatis01.mapper.UserMapper"></mapper>

2.4 主配置文件引入映射文件

在 SqlMapConfig.xml 中添加 AccountMapper.xml和UserMapper.xml:

<mappers><mapper resource="mappers/UserMapper.xml"/><mapper resource="mappers/AccountMapper.xml"/>
</mappers>
SqlMapConfig.xml的完整代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><!--这是主配置文件,主要做两件事--><!--第一件事是配置连接数据库的环境们--><!--第二件事是加载子配置文件--><!--配置连接数据库的环境们--><environments default="mysql"><!--配置具体的环境--><environment id="mysql"><!-- 配置事务管理类型 --><!--JDBC管理事务是关闭自动提交,改成手动提交--><transactionManager type="JDBC"/><!-- 配置是否需要使用连接池,POOLED使用,UNPOOLED不使用 --><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql:///mybatis_db"/><!--最后是数据库的名称--><property name="username" value="root"/><property name="password" value="2020"/></dataSource></environment></environments><!--加载子配置文件:加载映射的配置文件--><mappers><mapper resource="mappers/UserMapper.xml"/><mapper resource="mappers/AccountMapper.xml"/></mappers>
</configuration>

2.5 测试方法(这里我们在test.java.com.qcby.mybatis01文件夹下再创建一个AccountTest.java的测试类)

package com.qcby.mybatis01;import com.qcby.mybatis01.model.Account;
import com.qcby.mybatis01.mapper.AccountMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;import java.io.InputStream;
import java.util.List;public class AccountTest {@Testpublic void testFindAll() throws Exception {// 1. 加载主配置文件InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");// 2. 创建SqlSessionFactorySqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);// 3. 获取SqlSessionSqlSession session = factory.openSession();// 4. 获取Mapper代理对象AccountMapper mapper = session.getMapper(AccountMapper.class);// 5. 执行查询List<Account> accountList = mapper.findAll();for (Account account : accountList) {System.out.println(account);}// 6. 关闭资源session.close();inputStream.close();}
}

核心标签解析:association 实现多对一

在 AccountMapper.xml 中,<association> 标签是实现多对一关联的关键:

  • property="user":对应 Account 实体中的 user 属性

  • javaType="com.qcby.mybatis01.model.User":指定该属性的 Java 类型

  • 内部的 <result> 标签:映射 User 对象的字段与 SQL 查询结果的列名

当查询结果返回时,MyBatis 会自动将 username、address 封装到 User 对象中,再注入到 Account 的 user 属性中,实现关联数据的自动映射。

运行测试结果,得到如下的运行结果:

image

可以看到,每个账户都成功关联了所属用户的信息,多对一查询实现完成。

三、一对多查询:用户关联所有账户

3.1 需求说明

查询所有用户信息,同时获取每个用户的所有账户列表,最终返回包含账户集合的用户对象列表。

3.2 实体类改造:User 类添加账户集合属性

为了在查询用户时同时获取其所有账户,我们需要在 User 类中添加一个List<Account>类型的属性,表示该用户拥有的账户列表:

// 新增:用户拥有的账户列表(一对多关联)private List<Account> accounts;并实现这个属性的get和set方法

User.java的完整代码:

package com.qcby.mybatis01.model;import java.util.Date;
import java.util.List;
import java.io.Serializable;public class User implements Serializable {private Integer id;private String username;private Date birthday;private String sex;private String address;// 新增:用户拥有的账户列表(一对多关联)private List<Account> accounts;// Getter/Setterpublic Integer getId() { return id; }public void setId(Integer id) { this.id = id; }public String getUsername() { return username; }public void setUsername(String username) { this.username = username; }public Date getBirthday() { return birthday; }public void setBirthday(Date birthday) { this.birthday = birthday; }public String getSex() { return sex; }public void setSex(String sex) { this.sex = sex; }public String getAddress() { return address; }public void setAddress(String address) { this.address = address; }public List<Account> getAccounts() { return accounts; }public void setAccounts(List<Account> accounts) { this.accounts = accounts; }@Overridepublic String toString() {return "User{" +"id=" + id +", username='" + username + '\'' +", birthday=" + birthday +", sex='" + sex + '\'' +", address='" + address + '\'' +", accounts=" + accounts +'}';}
}

3.3 Mapper 接口与 XML 配置

3.3.1 UserMapper.java 接口

package com.qcby.mybatis01.mapper;import com.qcby.mybatis01.model.User;
import java.util.List;public interface UserMapper {// 查询所有用户,并关联其所有账户List<User> findAllUserWithAccount();
}

3.3.2 UserMapper.xml 配置文件(核心:collection 标签)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qcby.mybatis01.mapper.UserMapper"><!-- 一对多查询:查询用户及其所有账户 --><select id="findAllUserWithAccount" resultMap="userAccountMap">select u.*, a.id as aid, a.uid, a.money from user u left join account a on u.id = a.uid</select><!-- 自定义resultMap:处理一对多关联映射 --><resultMap id="userAccountMap" type="com.qcby.mybatis01.model.User"><!-- 用户基础字段映射 --><id property="id" column="id"/><result property="username" column="username"/><result property="birthday" column="birthday"/><result property="sex" column="sex"/><result property="address" column="address"/><!-- 一对多关联:映射账户集合 --><collection property="accounts" ofType="com.qcby.mybatis01.model.Account"><id property="id" column="aid"/><result property="uid" column="uid"/><result property="money" column="money"/></collection></resultMap></mapper>

3.4 测试方法(在test文件夹下创建UserAccountTest.java)

image

package com.qcby.mybatis01;import com.qcby.mybatis01.model.User;
import com.qcby.mybatis01.mapper.UserMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;import java.io.InputStream;
import java.util.List;public class UserAccountTest {@Testpublic void testFindAllUserWithAccount() throws Exception {// 1. 加载主配置文件InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");// 2. 创建SqlSessionFactorySqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);// 3. 获取SqlSessionSqlSession session = factory.openSession();// 4. 获取Mapper代理对象UserMapper mapper = session.getMapper(UserMapper.class);// 5. 执行查询List<User> userList = mapper.findAllUserWithAccount();for (User user : userList) {System.out.println(user);}// 6. 关闭资源session.close();inputStream.close();}
}

核心标签解析:collection 实现一对多

在 UserMapper.xml 中,<collection> 标签是实现一对多关联的关键:

  • property="accounts":对应 User 实体中的 accounts 属性(账户集合)

  • ofType="com.qcby.mybatis01.model.Account":指定集合中元素的 Java 类型

  • 内部的 <id> <result> 标签:映射 Account 对象的字段与 SQL 查询结果的列名(注意这里用了别名 aid 避免主键冲突)

当查询结果返回时,MyBatis 会自动将每个用户的所有账户封装成 List<Account> 集合,再注入到 User 的 accounts 属性中,实现一对多关联数据的自动映射。

执行结果

运行测试方法后,控制台会输出类似如下结果:

image

四、多对一 vs 一对多:核心区别总结

场景 关联标签 实体属性类型 业务含义
多对一 <association> 单个对象(如 User) 多个账户 → 一个用户
一对多 <collection> 集合对象(如 List<Account> 一个用户 → 多个账户

两者都是通过 resultMap 实现关联映射,区别仅在于关联对象的数量和类型。

http://www.jsqmd.com/news/678399/

相关文章:

  • Linux RT 调度器的 pushable_tasks:可推送任务列表的管理
  • 从LED流水灯到数据校验:手把手用Matlab bitshift模拟嵌入式开发中的位操作
  • Windows 11安装终极指南:如何用MediaCreationTool.bat轻松绕过硬件限制
  • 别再只会用min(A)了!MATLAB找最小值这8种高级用法,数据分析效率翻倍
  • 别再手动拖Actor了!用UE4官方Python插件批量操作,效率翻倍(附常用脚本)
  • 惠州汽车防擦条模胚加工厂家 - 昌晖模胚
  • 告别商业授权:手把手教你为Jetson Nano自建Qt5.14.2+OpenGL嵌入式开发环境
  • ESP32 MicroPython玩转DS18B20温度传感器:从单节点到多节点串联的完整避坑指南
  • 【会议征稿通知 | 东北石油大学主办 | SPIE出版 | EI 、Scopus稳定检索】2026年智慧油气与可持续发展国际学术会议(SOGSD 2026)
  • Audacity降噪太慢?试试FFmpeg命令行批量处理100个音频文件的高效方案
  • 别再硬分‘是’或‘不是’了:用Python手把手实现FCM模糊聚类,搞定鸢尾花分类难题
  • 从攻击者视角看防御:手把手复现一次MSF对Windows的渗透,然后教你如何发现和阻断它
  • 从DOTA v1.0到v2.0:手把手教你用YOLOv8训练自己的遥感目标检测模型
  • Linux RT 调度器的 highest_prio:当前最高优先级跟踪
  • go项目使用Jenkins进行CICD
  • 保姆级教程:在Windows 11上用VSCode+MinGW搞定LCM通信库(避坑指南)
  • Windows Cleaner:3分钟解决C盘爆红问题的终极免费方案
  • 从无人机避障到VR手柄:聊聊双目立体视觉中‘极线校正’为什么是性能瓶颈的救星
  • 别再让CPU干杂活了!聊聊DPU如何帮你把网络、存储、安全这些‘脏活累活’从服务器CPU上卸下来
  • 用STM32CubeMX和Max7219点亮16x16 LED点阵:一个完整项目的硬件焊接与软件调试避坑指南
  • CF1370F The Hidden Pair 解题报告:祝贺我首次切出 2700!
  • Bootstrap自采样:用R语言从零模拟,搞懂这个统计‘黑魔法’到底在做什么
  • 别再硬编码半径了!用Cesium的CallbackProperty实现鼠标拖拽画圆(附完整代码)
  • CMake条件判断避坑指南:从‘23a EQUAL 23’的诡异结果说起
  • 思源宋体TTF终极指南:7种字重免费商用中文排版解决方案
  • SAP OOALV隐藏按钮避坑指南:别再用`no_toolbar`了,这才是正确姿势
  • 手把手教你复现UEditor 1.4.3.3的XML上传漏洞:从XSS到SSRF的实战演练
  • 保姆级教程:用SSH远程连接你的WSL2,并配置端口转发实现外网访问(附常见错误排查)
  • 3步实现微信平板模式:免Root安卓多设备登录终极方案
  • 2026年蜂窝板防潮技术实测解析与批发价参考:吊顶包工包料/吊顶铝扣板/商铺蜂窝板吊顶/墙面蜂窝板/奶油风吊顶/选择指南 - 优质品牌商家