10.1.2 一对多
在实际项目开发中,一对多是非常常见的关系,比如,一个班级可以有多个学生个学生只能属于一个班级,班级和学生之间是一对多的关系,而学生和班级之间是多对的关系。在数据库中一对多关系通常使用主外键关联,外键列应该在多方,即多方维护关系。下面我们就用一个示例来看看MyBatis
怎么处理一对多关系。
示例:OneToManyTest
创建数据库表
首先,给之前创建的mybatis
数据库创建两个表tb_clazz
和tb_student
,并插入测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| use mybatis;
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS `tb_clazz`; CREATE TABLE `tb_clazz` ( `id` int(11) NOT NULL AUTO_INCREMENT, `code` varchar(18) NOT NULL, `name` varchar(18) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `tb_clazz` VALUES ('1', 'B151516', 'Java基础班');
DROP TABLE IF EXISTS `tb_student`; CREATE TABLE `tb_student` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(18) DEFAULT NULL, `sex` varchar(18) DEFAULT NULL, `age` int(11) DEFAULT NULL, `clazz_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `clazz_id` (`clazz_id`), CONSTRAINT `tb_student_ibfk_1` FOREIGN KEY (`clazz_id`) REFERENCES `tb_clazz` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `tb_student` VALUES ('1', '小明', '男', '24', '1'); INSERT INTO `tb_student` VALUES ('2', '小王', '男', '23', '1'); INSERT INTO `tb_student` VALUES ('3', '小芳', '女', '22', '1'); INSERT INTO `tb_student` VALUES ('4', '小丽', '女', '22', '1');
|
tb_student
表的clazz_id
作为外键参照tb_clazz
表的主键id
.
在mybatis
数据库中执行SQL
脚本,完成创建数据库和表的操作。
接下来,创建一个clazz
对象和一个Student
对象分别映射tb_clazz
和tb_student
表.
创建持久化对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class Student implements Serializable { private static final long serialVersionUID = 1L; private Integer id; private String name; private String sex; private Integer age; private Clazz clazz; public Student() { super(); } }
|
学生和班级之间是多对一的关系,即一个学生只属于一个班级。在Student
类当中定义了一个clazz
属性,该属性是一个Clazz
类型,用来映射多对一的关联关系,表示该学生所属的班级
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Clazz implements Serializable { private static final long serialVersionUID = 1L; private Integer id; private String code; private String name; private List<Student> students; public Clazz() { super(); } }
|
班级和学生之间是一对多的关系,即一个班级可以有多个学生。在Clazz
类当中定义了一个students
属性,该属性是一个List
集合,用来映射一对多的关联关系,表示一个班级有多个学生.
创建XML映射文件
再接下来是XML
映射文件。
ClazzMapper.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.fkit.mapper.ClazzMapper"> <select id="selectClazzById" parameterType="int" resultMap="clazzResultMap"> SELECT * FROM tb_clazz WHERE id = #{id} </select> <resultMap type="org.fkit.domain.Clazz" id="clazzResultMap"> <id property="id" column="id"/> <result property="code" column="code"/> <result property="name" column="name"/> <collection property="students" javaType="ArrayList" ofType="org.fkit.domain.Student" column="id" select="org.fkit.mapper.StudentMapper.selectStudentByClazzId" fetchType="lazy"> <id property="id" column="id"/> <result property="name" column="name"/> <result property="sex" column="sex"/> <result property="age" column="age"/> </collection> </resultMap> </mapper>
|
ClazzMapper.xml
中定义了一个select
标签,其根据id
查询班级信息。由于Clazz
类除了简单的属性id
、code
、name
以外,还有一个关联对象students
,所以返回的是一个id为clazzResultMap
的resultMap
。由于students
是一个List
集合,所以clazzResultMap
中使用了collection
元素映射一对多的关联关系,
collection标签详解
属性
property
属性用于指定一个持久化对象的属性,select
语句的查询的结果将会封装到这个属性中.
javaType
属性用于指定属性的类型,也就是students
变量的类型是ArrayList
类型.
ofType
属性用于指定属性的位置(也就是指定属性定义的类),ofType="org.fkit.domain.Student"
表示students
属性是org.fkit.domain.Student
的属性。
column
属性要查询的数据库表的列名。
select
属性表示会使用column
属性值id
作为参数执行StudentMapper
中定义的id
为selectStudentByClazzId
的select
标签,以便查询该班级对应的所有学生数据,
- 查询出的数据将被封装到
property
表示的students
对象当中。
- 还使用了一个新的属性
fetchType
,该属性的取值有eager
和lazy
,
eager
表示立即加载,即查询Clazz
对象的时候,会立即执行关联的selectStudentByClazzId
中定义的SQL
语句去査询班级的所有学生;
lazy
表示懒加载,其不会立即发送SQL
语句去查询班级的所有学生,而是等到需要使用到班级的students
属性时,才会发送SQL
语句去查询班级的所有学生。
fetch
机制更多的是为了性能考虑,
- 如果查询班级时确定会访问班级的所有学生则该属性应该被设置为
eager
;
- 如果査询班级时只是查询班级信息,有可能不会访问班级的所有学生,则该属性应该被设置为
lazy
。
- 正常情况下,一对多所关联的集合对象,都应该被设置成
lazy
。
MyBatis根配置文件中支持懒加载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration> <settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> </settings> </configuration>
|
设置说明
lazyLoadingEnabled
属性表示延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。默认为false
。这里设为true
表示延迟加载关联对象.
aggressiveLazyLoading
属性启用时,会使带有延迟加载属性的对象立即加载;反之,每种属性将会按需加载。默认为true
,所以这里需要设置成false
,表示按需加载。
StudentMapper.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.fkit.mapper.StudentMapper"> <select id="selectStudentById" parameterType="int" resultMap="studentResultMap"> SELECT * FROM tb_clazz c,tb_student s WHERE c.id = s.clazz_id AND s.id = #{id} </select> <select id="selectStudentByClazzId" parameterType="int" resultMap="studentResultMap"> SELECT * FROM tb_student WHERE clazz_id = #{id} </select> <resultMap type="org.fkit.domain.Student" id="studentResultMap"> <id property="id" column="id"/> <result property="name" column="name"/> <result property="sex" column="sex"/> <result property="age" column="age"/> <association property="clazz" javaType="org.fkit.domain.Clazz"> <id property="id" column="id"/> <result property="code" column="code"/> <result property="name" column="name"/> </association> </resultMap> </mapper>
|
StudentMapper.xml
中定义了一个id="selectStudentById"
的select
标签,其会根据学生id
查询学生信息,由于Student
类除了简单的属性id
、name
、sex
和age
之外,还有一个关联对象clazz
,所以它返回的是一个名为studentResultMap
的resultMap
.
提示
在实际开发中,一对多
关系通常映射为集合对象
,而由于多方的数据量可能很大,所以通常使用懒加载;而多对一
只是关联到一个对象
,所以通常使用多表连接直接把数据提取出来。
创建mapper接口对象
ClazzMapper接口
1 2 3 4
| public interface ClazzMapper { Clazz selectClazzById(Integer id); }
|
StudentMapper接口
1 2 3 4 5 6 7 8
| public interface StudentMapper {
Student selectStudentById(Integer id); }
|
测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| public class OneToManyTest { public static void main(String[] args) { SqlSession sqlSession = null; try { sqlSession = FKSqlSessionFactory.getSqlSession(); OneToManyTest t = new OneToManyTest(); t.testSelectClazzById(sqlSession); sqlSession.commit(); } catch (Exception e) { sqlSession.rollback(); e.printStackTrace(); } finally { if (sqlSession != null) sqlSession.close(); } } public void testSelectClazzById(SqlSession sqlSession) { ClazzMapper cm = sqlSession.getMapper(ClazzMapper.class); Clazz clazz = cm.selectClazzById(1); System.out.println(clazz.getId() + " " + clazz.getCode() + " " + clazz.getName()); List<Student> students = clazz.getStudents(); students.forEach(stu -> System.out.println(stu)); } public void testSelectStudentById(SqlSession sqlSession) { StudentMapper sm = sqlSession.getMapper(StudentMapper.class); Student stu = sm.selectStudentById(1); System.out.println(stu); System.out.println(stu.getClazz()); } }
|
在OneToManyTest
类中定义了一个testSelectClazzById()
方法,该方法用于测试一对多关系,查询班级Clazz
(一)的时候关联查询学生Student
(多)的信息。
运行结果
在main
方法中运行testSelectStudentById()
方法,其通过SqlSession
的getMapper(Class<T> type)
方法获得mapper
接口的代理对象ClazzMapper
,调用selectClazzById
方法时会执ClazzMapper.xml
中selectid="selectClazzById"
的元素中定义的SQL
语可。控制台显示如下:
1 2 3 4 5
| DEBUG [main] ==> Preparing: SELECT * FROM tb_clazz c,tb_student s WHERE c.id = s.clazz_id AND s.id = ? DEBUG [main] ==> Parameters: 1(Integer) DEBUG [main] <== Total: 1 Student [id=1, name=Java基础班, sex=男, age=24] Clazz [id=1, code=B151516, name=Java基础班]
|
在main
方法中运行testSelectStudentById()
方法,控制台显示如下:
1 2 3 4 5 6 7 8 9 10 11
| DEBUG [main] ==> Preparing: SELECT * FROM tb_clazz WHERE id = ? DEBUG [main] ==> Parameters: 1(Integer) DEBUG [main] <== Total: 1 1 B151516 Java基础班 DEBUG [main] ==> Preparing: SELECT * FROM tb_student WHERE clazz_id = ? DEBUG [main] ==> Parameters: 1(Integer) DEBUG [main] <== Total: 4 Student [id=1, name=小明, sex=男, age=24] Student [id=2, name=小王, sex=男, age=23] Student [id=3, name=小芳, sex=女, age=22] Student [id=4, name=小丽, sex=女, age=22]
|
可以看到,MyBatis
执行了一个多表查询语句,并且将查询到的班级信息封装到了学生对象的关联属性中。
总结
collection标签
collection标签的属性
-property
属性指定集合的变量名
-javaType
属性指定集合的类型
-ofType
属性指定集合中存放的元素的类型
-select
属性指定关联查询的select
标签
-column
属性指定要将哪一列,作为select
标签关联的查询语句的参数.
collection标签的子标签
这些子标签表示集合中元素的各个属性.
association标签
对于多对一,一对一联系,使用多表连接查询
即可.
association标签的属性
property
设置持久化对象的属性
javaType
设置该属性的类型
association标签的子标签
这些子标签表示关联的持久化对象的各个属性.
原文链接: 10.1.2 一对多