MyBatis 中多对一与一对多的数据处理详解
在开发基于 MyBatis 的项目时,经常需要处理表之间的关联关系。本文将详细介绍如何通过 resultMap 标签中的 association 和 collection 元素,实现多对一(学生与老师)和一对多(老师与学生)数据的正确处理,并提供具体的 SQL 定义、Java 实体类示例及测试结果。
多对一的映射处理
在多对一关系中,多个实体指向同一个父实体。以“多个学生对应一个老师”为例,从学生的角度来看,这是一个典型的多对一关系。
第1步:数据库设计与搭建环境
首先需要建立对应的数据表。教师表用于存储老师信息,学生表包含一个外键 tid 关联到教师表。
数据库脚本如下:
CREATE TABLE `teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO teacher(`id`, `name`) VALUES (1, '吴老师');
CREATE TABLE `student` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');
为了简化代码,推荐使用 Lombok 插件自动生成 getter/setter。引入 Maven 依赖并在实体类上添加 @Data 注解:
@Data
public class Teacher {
private int id;
private String name;
}
@Data
public class Student {
private int id;
private String name;
// 多个学生可以是同一个老师,即多对一
private Teacher teacher;
}
接下来创建对应的 Mapper 接口及 XML 配置文件(初始可为空),并注册到 MyBatis 配置文件中。
第2步:按查询嵌套处理(子查询方式)
这种方法类似于 SQL 中的子查询,先查学生,再通过外键查老师。
- 接口方法: 在
StudentMapper中添加获取所有学生的方法。
// 获取所有学生及对应老师的信息
public List getStudents();
- MXML 配置: 使用
<resultMap>配合<association>标签,利用select属性指定关联的查询 ID。
<select id="getStudents" resultMap="studentTeacher">
select * from mybatis.student;
</select>
<resultMap id="studentTeacher" type="student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!-- association: 关联属性 -->
<!-- javaType: 目标对象类型 -->
<!-- column: 在多的一方的表中的列名 (这里是学生表的tid) -->
<association property="teacher" column="tid" javaType="teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="teacher">
select * from mybatis.teacher where id = #{id};
</select>
- 测试验证:
@Test
public void getStudents(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> students = mapper.getStudents();
for (Student student : students) {
System.out.println(
"学生名:" + student.getName() +
"\t老师:" + student.getTeacher().getName());
}
sqlSession.close();
}
第3步:按结果嵌套处理(联表查询方式)
这种处理方式类似于 SQL 中的联表查询(Join),一次性查出所有需要的数据,然后在内存中进行封装。
- 接口方法:
public List<Student> getStudents2();
- MXML 配置: 直接编写联表 SQL,通过不同的
column别名区分字段,避免冲突。
<select id="getStudents2" resultMap="studentTeacher2">
SELECT s.id sid, s.name sname, t.id tid, t.name tname
FROM mybatis.student s, mybatis.teacher t
WHERE t.id = s.tid
</select>
<resultMap id="studentTeacher2" type="student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
</association>
</resultMap>
小结: 按照查询进行嵌套处理类似 SQL 的子查询;而按照结果进行嵌套处理类似 SQL 的联表查询。
一对多的映射处理
一对多关系通常指一个实体关联一组子实体。以“一个老师拥有多个学生”为例,对于老师来说,这就是一个一对多现象。
第1步:调整实体类结构
在学生表和老师的逻辑中翻转视角。学生类不再持有老师引用,而是老师类持有学生的集合。
@Data
public class Student {
private int id;
private String name;
private int tid;
}
@Data
public class Teacher {
private int id;
private String name;
// 一个老师多个学生
private List<Student> students;
}
第2步:按结果嵌套处理
这是最直观的实现方式,通过单条 SQL 查出老师和其下的所有学生。
- 接口定义:
// 获取指定老师,及老师下的所有学生
public Teacher getTeacher(int id);
- MXML 配置: 使用
<collection>标签来映射集合属性,注意指定ofType为集合内的泛型类型。
<select id="getTeacher" resultMap="TeacherStudent">
SELECT s.id sid, s.name sname, t.id tid, t.name tname
FROM mybatis.teacher t, mybatis.student s
WHERE t.id = s.tid AND t.id = #{tid}
</select>
<resultMap id="TeacherStudent" type="teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<!-- collection: 映射集合 -->
<!-- ofType: 指定映射到 list 集合属性中 pojo 的类型 -->
<collection property="students" ofType="student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
完成映射文件的注册后,进行测试:
@Test
public void testGetTeacher(){
SqlSession session = MybatisUtils.getSession();
TeacherMapper mapper = session.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacher(1);
System.out.println(teacher.getName());
System.out.println(teacher.getStudents());
}
第3步:按查询嵌套处理
同样可以使用子查询的方式来实现一对多。
<select id="getTeacher2" resultMap="TeacherStudent2">
SELECT * FROM teacher WHERE id = #{id}
</select>
<resultMap id="TeacherStudent2" type="Teacher">
<!-- column: 主键列名作为参数传递给子查询 -->
<collection property="students" javaType="ArrayList" ofType="Student"
column="id" select="getStudentByTeacherId"/>
</resultMap>
<select id="getStudentByTeacherId" resultType="Student">
SELECT * FROM student WHERE tid = #{id}
</select>
总结与注意事项
<association>用于一对一和多对一关系;<collection>用于一对多关系。javaType用于指定 POJO 中属性的具体类型(如单个对象);ofType用于指定映射到集合(List/Collection)属性中 POJO 的泛型类型。- 编写 SQL 时应保证可读性,并尽量考虑性能(如联表查询 vs 多次子查询)。
- 注意字段名与属性名不一致的问题,必要时在 SQL 中使用别名或在 resultMap 中手动映射。
- 建议开启日志功能(如 Log4j),以便排查 SQL 错误或执行流程问题。
以上就是 MyBatis 中多对一和一对多数据处理的详细内容,更多关于 MyBatis 多对一一对多处理技巧的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









