MyBatis一对多关联查询
以一个班级有多个学生为例
首先创建两张表
班级表
CREATE TABLE `t_class` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(20) NULL,
PRIMARY KEY (`id`));
学生表
cid为所在班级的主键
CREATE TABLE .`t_student` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(20) NULL,
`age` INT NULL,
`score` DOUBLE NULL,
`cid` INT NULL,
PRIMARY KEY (`id`));
创建实体类
package com.h3c.bean;
public class Student {
private int id;
private String name;
private int age;
private double score;
//省略constructor、getter、setter、toString方法
//注意constructor中的无参构造方法不能少
}
package com.h3c.bean;
public class MyClass {
private int id;
private String name;
//属于这个班级的学生集合
private List<Student> studentList;
//省略constructor、getter、setter、toString方法
//注意constructor中的无参构造方法不能少
}
注意如果二者是双向关联,即双方的属性中均有对方对象作为域属性出现,那么它们的toString()方法内部不要嵌套输出,否则会造成占内存溢出。
dao层
package com.h3c.dao;
import com.h3c.bean.MyClass;
public interface MyClassDao {
MyClass selectMyClassById(int id);
//List<Student> getStudents(int id);
}
编写MyClassMapper.xml映射文件
方式一:直接写多表联合查询sql
这种方式有几点需要说明:
- 1、.用分页插件pageHelper的时候,该种方式会导致查询错乱。
- 2、如果两张表中有字段名称相同,需要使用别名的方式区分,否则字段值可能被覆盖
<?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="com.h3c.dao.MyClassDao">
<resultMap id="ClassMap" type="myClass">
<!--MyClass中的基本属性-->
<id column="id" property="id"/>
<result column="name" property="name"/>
<!--关联属性的映射关系-->
<collection property="studentList" ofType="student">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="age" property="age"/>
<result column="score" property="score"/>
</collection>
</resultMap>
<select id="selectMyClassById" resultMap="ClassMap">
SELECT c.id cid,c.name cname,s.id sid,s.name sname,s.age sage,s.score sscore
FROM t_class c, t_student s
WHERE c.id = s.cid and c.id=#{id}
</select>
</mapper>
方式二:采用子查询的方式
这种方式不用关心多个表中有相同名称的字段的问题
<?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="com.h3c.dao.MyClassDao">
<resultMap id="ClassMap" type="myClass">
<id column="id" jdbcType="INTEGER" property="id"/>
<result column="name" jdbcType="VARCHAR" property="name"/>
<collection property="studentList"
column="id"
select="com.h3c.dao.MyClassDao.getStudents" />
</resultMap>
<resultMap id="StudentMap" type="student">
<id column="id" jdbcType="INTEGER" property="id" />
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="age" jdbcType="INTEGER" property="age" />
<result column="score" jdbcType="DOUBLE" property="score"/>
<result column="cid" jdbcType="INTEGER" property="cid"/>
</resultMap>
<select id="selectMyClassById" parameterType="com.h3c.bean.MyClass"
resultMap="ClassMap">
select id, name from t_class where id = #{id}
</select>
<select id="getStudents" resultMap="StudentMap" parameterType="int" >
select id, name, age, score from t_student where cid = #{id}
</select>
</mapper>
MyBatis延迟加载/懒加载
首先我们先思考一个问题,假设:一个用户有10个账户。
问题1:在查询用户的时候,是否要把关联的账户也查出来?
问题2:在查询账户的时候,是否要把关联的用户也查出来?
解答:
1、在查询用户的时候,用户下的账户信息应该是在我们需要使用的时候再去查询。
2、在查询账户的时候,账户的所属用户信息应该是随着账户查询时一起查询出来。
搞清楚这两个简单的问题后,我们就可以引出延迟加载和立即加载的特性。
- 立即加载:执行完对主加载对象的 select 语句,马上执行对关联对象的 select 查询
- 延迟加载:
- 侵入式延迟: 执行对主加载对象的查询时,不会执行对关联对象的查询。但当要访问主加载对象的详情属性时,就会马上执行关联对象的select查询。
- 深度延迟: 执行对主加载对象的查询时,不会执行对关联对象的查询。访问主加载对象的详情时也不会执行关联对象的select查询。只有当真正访问关联对象的详情时,才会执行对关联对象的 select 查询。
使用场景:在对应的四种表关系中,一对多、多对多通常情况下采用延迟加载,多对一、一对一通常情况下采用立即加载。
MyBatis延迟加载的实现
MyBatis通过在mybatis的全局配置文件中使用settings标签实现延迟加载,且只对于resultMap中的collection和association起作用。MyBatis延迟加载/懒加载需要使用多张表单独查询的方式,即上面的方式二。
下面来看一下具体配置参数:
参数 | 说明 | 默认值 |
---|---|---|
lazyLoadingEnabled | 延迟加载的全局开关。当开启(值为true)时,所有关联对象都会延迟加载。 特定关联关系中可通过设置collection和association标签中的 fetchType 属性来覆盖该项的开关状态,配置为lazy之后是开启深度延迟,配置eager是不开启深度延迟。 | false |
aggressiveLazyLoading | 值为true时,开启侵入式延迟加载;值为false时,开启深度延迟加载 | 3.4.1版本之前默认是true,之后默认是false |
测试
mybatis.xml中添加如下配置:
注意:需要放在<properties>标签和<typeAliases>标签之间
<!--全局参数设置-->
<settings>
<!--延迟加载总开关-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--开启侵入式延迟加载-->
<setting name="aggressiveLazyLoading" value="true"/>
</settings>
测试方法:
@Test
public void selectClassById(int id) {
MyClass myClass = myClassDao.selectMyClassById(1);
//触发id为getStudents对应的sql语句
System.out.println(myClass.getName());
}
//修改aggressiveLazyLoading的值为false,开启深度延迟加载
@Test
public void selectClassById(int id) {
MyClass myClass = myClassDao.selectMyClassById(1);
//不会触发id为getStudents对应的sql语句
System.out.println(myClass.getName());
//触发id为getStudents对应的sql语句
System.out.println(myClass.getStudentList().size());
}