我们在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);
经过上面的代码之后,我们对创建的两个表进行查看,结果如下:

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查看依赖是否导入

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

各部分的作用如下:

本次案例基于 用户 (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文件)

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 属性中,实现关联数据的自动映射。
运行测试结果,得到如下的运行结果:

可以看到,每个账户都成功关联了所属用户的信息,多对一查询实现完成。
三、一对多查询:用户关联所有账户
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)

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 属性中,实现一对多关联数据的自动映射。
执行结果
运行测试方法后,控制台会输出类似如下结果:

四、多对一 vs 一对多:核心区别总结
| 场景 | 关联标签 | 实体属性类型 | 业务含义 |
|---|---|---|---|
| 多对一 | <association> |
单个对象(如 User) | 多个账户 → 一个用户 |
| 一对多 | <collection> |
集合对象(如 List<Account>) |
一个用户 → 多个账户 |
两者都是通过 resultMap 实现关联映射,区别仅在于关联对象的数量和类型。
