在实际项目开发中,一对多是非常常见的关系,比如,一个班级可以有多个学生,一个学生只能属于一个班级,班级和学生是一对多的关系,而学生和班级是多对一的关系。数据库中一对多关系通常使用主外键关联,外键列应该在多方,即多方维护关系。下面我们就用一个简单示例来看看MyBatis怎么处理一对多关系。
首先,给在数据库创建两个表tb_clazz 和tb_student,并插入测试数据。SQL脚本如下:

CREATE TABIE tb_clazz(
id INT PRIMARY KEY AUTO_INCREMENT,
CODE VARCHAR(18),
NAME VARCHAR(18)
)
INSERT INTO tb_clazz(CODE,NAME) VALUES('201803','计算机专业');
CREATE TABLE tb_student(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(18),
sex VARCHAR(18),
age INT,
clazz_id INT,
FOREIGN KEY (clazz_id) REEERENCES tb_clazz(id)
)
INSERT INTO tb_student (NAME,sex,age,clazz_id) VALUES ('马云','男',23,1) ;
INSERT INTO tb_student (NAME,sex,age,clazz_id) VALUES('李飞飞','女',23,1) ;
INSERT INTO tb_student (NAME,sex,age,clazz_id VALUES( '马化腾','男',23,1) ;
INSERT INTO tb_student (NAME,sex,age,clazz_id) VALUES('柳青','女',23,1) ;

提示:
tb_student表的clazz_id作为外键参照tb_clazz表的主键id。

在数据库中执行SQL脚本,完成创建数据库和表的操作。接下来,创建一个Clazz对象和一个Student对象分别映射tb_clazz和tb_student表。

public class Clazz implements Serializable{
private Integer id; // 班级id,主键
private String code; // 班级编号
private String name; // 班级名称
// 班级和学生是一对多的关系,即一个班级可以有多个学生
private List<Student> students;
// 省略构造器和set/get 方法......
}

班级和学生是一对多的关系,即一个班级可以有多个学生。在Clazz类当中定义了一个students属性,该属性是一个List集合,用来映射一对多的关联关系,表示一个班级有多个学生。

public class Student implements Serializable {
private Integer id; // 学生id,主键
private String name; // 姓名
private String sex; // 性别
private Integer age; // 年龄
// 学生和班级是多对一的关系,即一个学生贝属于一个班级
private Clazz clazz;
// 省略构造器和set/get 方法.....
}

学生和班级是多对一的关系,即一个学生只属于一个班级。在Student类当中定义了一个clazz属性,该属性是一个Clazz类型,用来映射多对一的关联关系,表示该学生所属的班级。
再接下来是XML映射文件。

<mapper namespace="cn.mybatis.mapper.clazzMapper">
<!-- 根据id查询班级信息,返回resultMap-->
<select id="selectClazzById" parameterType="int" resultMap="clazzResultMap">
SELECT * FROM tb_clazz WHERE id = #{id}
</select>

<!-- 映射Clazz对象的resultMap-->
<resuItMap id="clazzResultMap" type="cn.mybatis.domain.Clazz">
<id property="id" column="id" />
<result property="code" column= "name" />
<result property="name" column= "name" />
<!--一对多关联映射:collection fetchType="lazy"表示懒加载-->
<collection property="students" javaType="ArrayList"
column="id" ofType="cn.mybatis.domain.Student"
select="cn.mybatis.mapper.StudentMapper.selectStudentByClazzId" fetchType="lazy">
<id property="id" column="id" />
<result property="name" column= "name" />
<reault property="sex" column="sex"/>
<result property="age" column="age"/>
</collection>
</resultMap>
</mapper>

ClazzMapper.xml中定义了一个<select.../>,其根据id查询班级信息。由于Clazz类除了简单的属性id、code、name 之外,还有一个关联对象students,所以返回的是一个名为clazzResultMap的resultMap。由于student是一个List集合,所以clazzResultMap中使用了<collection.../>元素映射一对多的关联关系,select属性表示会使用column属性的id值作为参数执行StudentMapper中定义的selectStudentByClazz查询该班级对应的所有学生数据,查询出的数据将被封装到property表示的students对象当中。
还使用了一个新的属性fetchType,该属性的取值有eager和lazy,eager表示立即加载,即查询Clazz对象的时候,会立即执行关联的selectStudentByClazzId中定义的SQL语句去查询班级的所有学生;lazy表示懒加载,其不会立即发送SQL语句去查询班级的所有学生,而是等到需要使用到班级的students属性时,才会发送SQL语句去查询班级的所有学生。fetch机制更多的是为了性能考虑,如果查询班级时确定会访问班级的所有学生,则该属性应该设置为eager;如果查询班级时只是查询班级信息,有可能不会访问班级的所有学生,则该属性应该设置为lazy。正常情况下,一对多所关联的集合对象,都应该被设置成lazy。
使用懒加载还需要在mybatis-config.xm 中增加如下配置:

<settings>
<!-- 要使延迟加载生效必须配置下面两个属性-->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false" />
</settings>

lazyLoadingEnabled属性表示延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。默认为false.
agressiveLazyLoading属性启用时,会使带有延迟加载属性的对象立即加载; 反之,每种属性将会按需加载。默认为true,所以这里需要设置成false。

<mapper namespace="cn.mybatis.mapper.studentMapper">
<!-- 根据id查询学生信息,多表连接,返回reaultMap-->
<select id="selectStudentById" parameterType="int" resultMap="studentResuItMap" >
SELECT * FROM tb_clazz c,tb_student s
WHERE c.id = s.clazz_id
AND s.id = #{id}
</select>
<!-- 根据班级id查询学生信息,返回resultMap-->
<select id="selectstudentByClazzId" parameterType="int" resultMap="studentResultMap">
SELECT * FROM tb_student WHERE clazz_id = #{id}
</select>
<!-- 映射Student对象的resultMap-->
<resultMap id="studentResultMap" type="cn.mybatis.domain.Student"  >
<id property = "id" column="id" />
<result property="sex" column="sex" />
<result property="age" column="age" />
<!-- 多对一关联映射:association -->
<association property="clazz" javaType="cn.mybatis.domain.Clazz">
<id property = "id" column="id" />
<result property="code" column="code" />
<result property="name" column="name" />
</association>
</resultMap>
</mapper>

StudentMapper.xm 中定义了一个<select id="selectStudentById".../> 其会根据学生id查询学生信息,由于Student类除了简单的属性id、name、sex和age之外,还有一个关联对象clazz,所以它返回的是一个名为clazzResultMap的resultMap。clazzResutMap中使用了<association.../>元素映射多对一的关联关系,因为<select id="selectStudenById".../>的SQL 语句是一条多表连接,关联tb_clazz表的同时查询了班级数据,所以<association.../>只是简单地装载数据。
在实际开发中,一对多关系通常映射为集合对象,而由于多方的数据量可能很大,所以通常使用懒加载; 而多对一只是关联到一个对象,所以通常使用多表连接直接把数据提取出来。
StudentMapper.xml 中还定义了一个<select id="selectStudentByClazzId".../>其会根据班级id查询所有学生信息,该查询用于ClazzMapper.xml 中的关联查询。
再接下来是mapper接口对象。

public interface ClazzMapper{
// 根据id查询班级信息
Clazz selectclazzById(Integer id);
}
public interface StudentMapper{
// 根据id查询学生信息
Student selectStudentById(Integer id);
}

最后,完成测试类。

public class OneToManyTest{
public static void main(String[] args) throws Exception
// 读取mybatis-config.xm1文件
Inputstream inputstream = Resources.getResourcenStream("mybatis-config.xml")
// 初始化mybatis,创建SqlSessionFactory 类的实例
sqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputstream);
// 创建Session实例
SqlSession session = sqlSessionFactory.openSession();
OneToManyTest t = new OneToManyTest() ;
//测试一对多
t.testSelectClazzById(session);
// 提交事务
seeaion.commit();
// 关闭Session
session.close();
}
// 测试一对多,查询班级Clazz(一)的时候级联查询学生student (多)
public vold testSelectClazzById(SqlSession session){
// 获得ClazzMapper接口的代理对象
clazzMapper cm = session.getMapper(ClazzMapper.class) ;
// 调用selectClazzById 方法
Clazz clazz = cm.selectClazzById(1);
// 查看查询到的c1azz 对象信息
System.out.println(clazz.getId() + " "+ clazz.getCode() + " "+clazz.getName());
}

在OneToManyTest类中定义了一个testSelectClazzById()方法,该方法用于测试一对多关系,查询班级Clazz(一)的时候级联查询学生Student(多) 的信息。运行OneToManyTest类的main方法,其通过SqlSession的getMapper(Class<Type> t)方法获得mapper接口的代理对象ClazzMapper,调用selectClazzByld方法时会执行ClazzMapper.xml的<select id="selectClazzById" .../>中定义的sql语句。控制台显示如下:
DEBUG [main]==>Preparing: SELECT * PROM tb_clazz WHERE id = ?
DEBUG [main]-->Parameters: 1(Integer)
DEBUG [main]<==Total :1
1 201803 计算机专业
可以看到,MyBatis只是执行了查询班级的SQL语句,由<select id="selectClazzById" .../>中的<colletion fethType=lazy">使用的是懒加载,因此,当没有使用到关联的学生对象时,并没有发送查询学生的SQL语句。
修改testSelectClazzByld方法,增加访问关联学生的代码:

public void testSelectClazzById(SqlSession session) {
// 获得ClazzNappex接口的代理对象
ClazzMapper cm = session.getMapper(ClazzMapper.class);
// 调用selectClazzById方法
Clazz clazz = cm.selectclazzById(l);
// 查看查询到的C1azz对象信息
system.out.println(clazz.getId( + " "+ clazz.getCode() + " "+clazz.getName() );
// 查看clazz对象关联的学生信息
List<Student> students = clazz.getstudents();
    for(Student stu : students)
    {
        System.out.println(stu);
    }
}

再次运行OneToManyTest类的main方法。控制台显示如下:
DEBUG [main]--> Preparing: SBIECT * FRON tb_clazz WHERE id = ?
DEBUG [main]==> Parameters: l(Integer)
DEBUG [main]<== Total :
1 201803 计算机专业
DEBUG [main]--> Preparing: SELECT * FRON tb_student WHERE clazz id = ?
DEBUG [mainj--> Parameters: l(Integer)
DEBUG [main]<== Total: 4
student [id=1,name=马云,sex=男,age=23]
student [id=2,name=李飞飞,sex=女,age=23]
Student [id=3,name=马化腾,sex=男,age=23]
Student [id=4,name=柳青,sex=女,age=23]
可以看到,MyBatis执行了查询班级的SQL语句之后,又执行了根据clazz_id 查询学生信息的SQL语句。这就是所谓的懒加载。增加一个testSelectStudentBy方法,测试多对一。

// 测试多对一,查询学生Student(多) 时级联查询班级Clazz(一)
public void testSelectStudentById(SqlSession session) {
获得StudentMapper 接口的代理对象
StudentMapper sm = session.getMapper(StudentMapper.class) ;
// 调用selectstudentById 方法
Student stu = sm.selectStudentById(1) ;
// 查看查询到的Student对象信息
System.out.println(stu) ;
// 查看student对象关联的班级信息
System.out.println(stu.getclazz());
}

在main方法中运行testSelectStudentById方法,控制台显示如下:
DEBUG[main]---> Preparing: SELECT * FRON tb_clazz c,tb_student s WHERE c.id = s.clazz_id AND s.id = ?
DEBUG[main]---> Parameters: 1(Integer)
DEBUG[main]<--- Total:
Student[id=1,name=马云,sex=男,age=23]
Clazz[id=1,code=201803,name=计算机专业]
可以看到,MyBatis执行了一个多表查询语句,并且将查询到的班级信息封装到了学生对象的关联属性中。

标签: none

添加新评论