一、概述
多对一(many-to-one)是数据库设计中常见的关联关系。例如:多个员工(employee)属于同一个部门(department)。在 mybatis 中,多对一映射通常使用 <association> 标签来实现,其配置方式与一对一(one-to-one)几乎完全相同。
多对一与一对一的区别在于业务语义:
- 一对一:一个员工对应一个身份证(双向唯一)。
- 多对一:多个员工对应同一个部门(从“多”方看向“一”方)。
二、场景与实体类
2.1 数据库表结构
-- 部门表(一方)
create table t_department (
id bigint primary key auto_increment,
dept_name varchar(100),
location varchar(100)
);
-- 员工表(多方)
create table t_employee (
id bigint primary key auto_increment,
emp_name varchar(100),
salary decimal(10,2),
dept_id bigint, -- 外键,指向 t_department.id
foreign key (dept_id) references t_department(id)
);2.2 java 实体类
// 部门实体(一方)
public class department {
private long id;
private string deptname;
private string location;
// getter/setter
}
// 员工实体(多方)
public class employee {
private long id;
private string empname;
private double salary;
private department department; // 多对一:多个员工属于同一个部门
// getter/setter
}
三、两种映射方式
3.1 方式一:嵌套结果(join 查询,推荐)
使用一次 join 查询,一次性获取员工和部门的所有字段,性能最优。
xml 配置
<resultmap id="employeedeptmap" type="employee">
<!-- 员工字段映射 -->
<id property="id" column="e_id"/>
<result property="empname" column="e_name"/>
<result property="salary" column="salary"/>
<!-- 多对一:每个员工关联一个部门 -->
<association property="department" javatype="department">
<id property="id" column="d_id"/>
<result property="deptname" column="dept_name"/>
<result property="location" column="location"/>
</association>
</resultmap>
<select id="selectemployeewithdept" resultmap="employeedeptmap">
select
e.id as e_id,
e.emp_name as e_name,
e.salary,
d.id as d_id,
d.dept_name,
d.location
from t_employee e
left join t_department d on e.dept_id = d.id
where e.id = #{id}
</select>生成的 sql
select e.id as e_id, e.emp_name as e_name, e.salary,
d.id as d_id, d.dept_name, d.location
from t_employee e
left join t_department d on e.dept_id = d.id
where e.id = ?优点
- 一次查询,性能最高。
- 数据完整性好,没有
n+1问题。
3.2 方式二:嵌套查询(分步查询)
先查员工,再根据 dept_id 单独查部门。可以配合延迟加载实现按需查询。
xml 配置
<resultmap id="employeedeptlazymap" type="employee">
<id property="id" column="id"/>
<result property="empname" column="emp_name"/>
<result property="salary" column="salary"/>
<!-- 多对一:通过另一个查询获取部门信息 -->
<association property="department"
javatype="department"
select="com.xie.mapper.departmentmapper.selectbyid"
column="dept_id"
fetchtype="lazy"/> <!-- 延迟加载 -->
</resultmap>
<select id="selectemployeelazy" resultmap="employeedeptlazymap">
select id, emp_name, salary, dept_id
from t_employee
where id = #{id}
</select>对应的 departmentmapper:
<select id="selectbyid" resulttype="department">
select * from t_department where id = #{id}
</select>优点
- sql 职责清晰,复用性强(
selectbyid可单独使用)。 - 配合
fetchtype="lazy",可实现按需加载。
缺点
存在 n+1 查询风险(查询多个员工时,会额外执行多次部门查询)。
四、延迟加载配置
4.1 全局配置(mybatis-config.xml)
<settings>
<!-- 开启延迟加载 -->
<setting name="lazyloadingenabled" value="true"/>
<!-- 关闭激进加载,只在实际访问时触发 -->
<setting name="aggressivelazyloading" value="false"/>
</settings>4.2 局部配置(覆盖全局)
在 <association> 中通过 fetchtype 精细化控制:
<!-- 立即加载(即使开启全局延迟,此关联也会立即加载) --> <association property="department" fetchtype="eager" .../> <!-- 延迟加载(覆盖全局设置) --> <association property="department" fetchtype="lazy" .../>
五、级联属性操作(cascade)
在多对一关系中,级联属性通常指:当操作(插入/更新)员工时,如何处理其关联的部门对象。
5.1 查询时的级联属性
场景:查询员工时,需要同时返回部门信息(上文已实现)。
5.2 插入时的级联属性
场景:插入员工时,需要为员工关联一个已存在的部门。
mapper 接口
public interface employeemapper {
int insertemployee(employee employee);
}
xml 配置
<insert id="insertemployee" usegeneratedkeys="true" keyproperty="id">
insert into t_employee (emp_name, salary, dept_id)
values (#{empname}, #{salary}, #{department.id})
<!-- 注意:通过 #{department.id} 获取关联部门的 id -->
</insert>调用代码
// 先查询或创建一个部门
department dept = departmentmapper.selectbyid(1l);
// 或 department dept = new department(); dept.setid(2l);
employee emp = new employee();
emp.setempname("李四");
emp.setsalary(8000.0);
emp.setdepartment(dept); // 设置关联部门
employeemapper.insertemployee(emp);
sqlsession.commit();
生成的 sql:
insert into t_employee (emp_name, salary, dept_id) values (?, ?, ?) -- 参数:李四, 8000.0, 1
5.3 更新时的级联属性
场景:更新员工所属部门(更换部门)。
<update id="updateemployeedept">
update t_employee
set dept_id = #{department.id}
where id = #{id}
</update>
调用代码
employee emp = employeemapper.selectbyid(1l); // 更换部门 department newdept = departmentmapper.selectbyid(2l); emp.setdepartment(newdept); employeemapper.updateemployeedept(emp); sqlsession.commit();
六、多对一 vs 一对多 对比
| 对比维度 | 多对一 | 一对多 |
|---|---|---|
| 视角 | 从“多”方看向“一”方 | 从“一”方看向“多”方 |
| 实体类属性 | private department department; | private list<employee> employees; |
| xml 标签 | <association> | <collection> |
| 标签属性 | javatype="department" | oftype="employee" |
| 典型场景 | 员工 → 部门 | 部门 → 员工列表 |
| 外键所在表 | 在“多”方表中(t_employee.dept_id) | 在“多”方表中(t_employee.dept_id) |
七、常见错误与避坑指南
7.1 忘记配置<id>导致数据重复
<association property="department" javatype="department">
<!-- ❌ 缺少 id,mybatis 无法识别同一部门 -->
<result property="deptname" column="dept_name"/>
</association>
<!-- ✅ 必须配置 id -->
<association property="department" javatype="department">
<id property="id" column="d_id"/>
<result property="deptname" column="dept_name"/>
</association>7.2 javatype写错
<!-- ❌ 错误:department 是 department 类型,不是 list --> <association property="department" javatype="list"> <!-- ✅ 正确 --> <association property="department" javatype="department">
7.3 列名冲突(多表 join 时)
-- ❌ 错误:两表都有 id,覆盖 select e.*, d.* from t_employee e left join t_department d on ... -- ✅ 正确:使用别名区分 select e.id as e_id, d.id as d_id, ...
八、最佳实践总结
| 要点 | 建议 |
|---|---|
| 映射方式 | 优先使用嵌套结果(join),一次查询,性能最优 |
| 延迟加载 | 仅当关联数据访问频率低时使用,需开启全局配置 |
| 关联字段 | 外键字段(如 dept_id)需在数据库中正确建立 |
| 插入/更新 | 通过 #{department.id} 获取关联对象的 id |
| n+1 问题 | 使用嵌套查询时,注意控制数据量,避免循环查询 |
结语
多对一映射是 mybatis 中最常见的关联关系之一,其本质与一对一相同,只是业务语义上代表了“多个子对象属于一个父对象”。掌握 <association> 的两种配置方式(嵌套结果与嵌套查询),并合理运用延迟加载机制,能够让你在面对复杂关联查询时游刃有余。
到此这篇关于mybatis中多对一关联映射实现详解的文章就介绍到这了,更多相关mybatis多对一关联映射内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论