一、多对一映射关系
POJO 中的属性可能会是一个对象,当我们查询多方的信息时,还想要知道一方的信息,就需要用到一对多查询。
我们可以使用联合查询,并以级联属性的方式封装对象,使用 association 标签来定义对象的封装规则。
两个 JavaBean 类:
Employee 类
public class Employee {
private Integer id;
private String lastName;
private String gender;
private String email;
private Department department;
//省略了 get/set 方法和构造器
}
Department 类
public class Department {
private Integer id;
private String deptName;
private List<Employee> emps;
//省略了 get/set 方法和构造器
}
当我们想要获取员工同时还要获取其对应的部门信息,这个一个多对一的关系,我们就可以使用下面的几种方式来实现。
二、使用级联属性封装的方式查询
mapper 中的方法:
//根据id查询员工和所在的部门信息
public Employee getEmpById(Integer id);
mapper.xml 中的配置:
<!--
场景一:
查询Employee的同时查询员工对应的部门
Employee === Department
一个员工有与之对应的部门信息
-->
<!--
联合查询:级联属性封装结果集
-->
<resultMap id="MyDifEmp" type="com.njf.mybatis.bean.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email" />
<result column="gender" property="gender"/>
<result column="did" property="department.id"/>
<result column="dept_name" property="department.deptName"/>
</resultMap>
<!--
public Employee getEmpAndDept(Integer id);
-->
<select id="getEmpAndDept" resultMap="MyDifEmp">
SELECT e.`id` id, e.`last_name` last_name, e.`gender` gender, e.`dept_id` dept_id,
d.`id` did, d.`dept_name` dept_name
FROM tbl_employee e, tbl_department d
WHERE e.`dept_id`=d.`id` AND e.`id` = #{id}
</select>
说明:
resultMap:自定义映射,用于自定义某个JavaBean的封装规则,处理复杂的表关系
id:用于设置主键的映射关系,定义主键底层会有优化,column 设置字段名(列),property 设置属性名,
result:设置非主键的映射关系,定义普通列封装规则,column 设置字段名(列),property 设置属性名
三、使用association 标签
修改 mapper.xml 中的配置信息:
<!--
使用 association 定义关联的单个对象的封装规则
-->
<resultMap id="MyDifEmp2" type="com.njf.mybatis.bean.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email" />
<result column="gender" property="gender"/>
<!--
association:可以指定联合的JavaBean对象
property="department":指定哪个属性是联合的对象
javaType:指定这个属性对象的类型【不能省略】
-->
<association property="department" javaType="com.njf.mybatis.bean.Department">
<id column="did" property="id"/>
<result column="dept_name" property="deptName"/>
</association>
</resultMap>
<!--
public Employee getEmpAndDept(Integer id);
-->
<select id="getEmpAndDept" resultMap="MyDifEmp2">
SELECT e.`id` id, e.`last_name` last_name, e.`gender` gender, e.`dept_id` dept_id,
d.`id` did, d.`dept_name` dept_name
FROM tbl_employee e, tbl_department d
WHERE e.`dept_id`=d.`id` AND e.`id` = #{id}
</select>
通过 association 会帮我们创建一个 property 类型的对象,接着给该对象的属性赋值,然后再赋值给 Emp(resultMap 的 type)。
四、association 分步查询
在实际的开发中,对于每个实体类都应该有具体的增删改查方法,也就说 DAO 层,因此,对于查询员工信息并且将对应的部门信息也查询出来的需求,就可以通过分步的方式完成查询。
① 先通过员工的 id 查询员工信息
② 再通过查询出来的员工信息中的外键(部门id)来查询对应的部门信息
在 DeptMapper 接口中声明方法:
//根据id查询部门信息
public Department getDeptById(Integer id);
在 DeptMapper 中配置:
<!--
public Department getDeptById(Integer id);
-->
<select id="getDeptById" resultType="Department">
select id, dept_name deptName from tbl_department where id = #{id}
</select>
在 EmployeeMapper 中声明分步查询的方法:
//分步查询:先查员工信息,再查部门信息
public Employee getEmpByIdStep(Integer id);
在 EmployeeMapper.xml 中配置:
<!--
使用 association 定义关联的单个对象的封装规则
使用 association 进行分步查询
1、先按照员工id查询员工信息
2、根据查询员工信息中的 dept_id 去部门表查出部门信息
3、部门设置到员工中
-->
<!--
association:定义关联对象的封装规则
property:要处理的属性
select:表明当前属性是调用 select 指定的方法查出的结果,分步查询的SQL的id
column:指定将哪一列的值传给这个方法
流程:使用select指定的方法(传入column指定的这列参数的值)查出对象,并封装给property指定的属性
-->
<resultMap id="MyEmpByStep" type="com.njf.mybatis.bean.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email" />
<result column="gender" property="gender"/>
<association property="department"
select="com.njf.mybatis.dao.DepartmentMapper.getDeptById"
column="dept_id">
</association>
</resultMap>
<!--
public Employee getEmpByIdStep(Integer id);
-->
<select id="getEmpByIdStep" resultMap="MyEmpByStep">
select * from tbl_employee where id=#{id}
</select>
说明:
association:定义关联对象的封装规则
property:要处理的属性
select:分步查询的SQL的id,即接口的全限定名.方法名或namespace.SQL的id
column:分步查询的条件,注意:此条件必须是从数据库查询过得(即调用的select方法的参数)指定将哪一列的值传给这个方法
流程:使用select指定的方法(传入column指定的这列参数的值)查出对象,并封装给property指定的属性。
五、association 分步查询使用延迟加载(懒加载,按需加载)
懒加载是只查询需要使用到的数据,如果使用不到,就不再查询。
如:若想要查询 Emp 中员工的信息(不包含部门),部门信息就不再查询。如果不使用懒加载,将把所有的数据都查询出来。
在分步查询的基础上,可以使用延迟加载来提升查询的效率,只需要在全局的 Setting 中进行如下的配置。
<settings>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/> // 默认false,不开启
<!-- 设置加载的数据是按需还是全部,是否查询所有数据 -->
<setting name="aggressiveLazyLoading" value="false"/> // 默认 true,查询所有
</settings>
设置说明:
<setting name="lazyLoadingEnabled" value="true"/>
是否开启延迟加载
<setting name="aggressiveLazyLoading" value="false"/>
aggressiveLazyLoading:侵入延迟加载,当使用任一属性,所有的属性都会加载;禁用的话,只有用到该属性才会加载,即按需加载
这两个标签一块使用,当把懒加载关闭了(false), aggressiveLazyLoading 能查询所有的数据。
如果开启了懒加载(true),但是 aggressiveLazyLoading 还是true,还是会将所有的数据查询出来。
所以,如果想要使用懒加载(true),aggressiveLazyLoading 必须为 false,即按需查询。
测试:
@Test
public void testResultMap() throws IOException {
//1、获取 sqlSessionFactory
SqlSessionFactory sqlSessionFactory = getsqlSessionFactory();
//2、获取 sqlSession 实例,能直接执行已经映射的 SQL 语句
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
//3、获取接口的实现类对象
/**
* 推荐使用
* 会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
*/
EmployeeMapperPlus mapper = sqlSession.getMapper(EmployeeMapperPlus.class);
Employee empByIdStep = mapper.getEmpByIdStep(1);
System.out.println(empByIdStep);
System.out.println("empByIdStep = " + empByIdStep.getDepartment());
} finally {
sqlSession.close();
}
}
运行结果:
可以看到向数据库发送了两条SQL语句,如果不涉及 Employee的Department信息,就不会查询所在部门的信息。只有用到了Department信息时,才会把部门信息查询出来。(懒加载生效)
注意:延迟加载只对分步查询有效果。