持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第28天,点击查看活动详情
resultMap
在select标签中有一个非常重要的属性: resultMap ,它是用来自定义结果映射规则的,在今后的MyBatis开发中,我们需要大量使用该标签。
\
还记得最开始的案例中,因为lastName与last_name不一致使得MyBatis无法自动导入属性值,为此,我们开启了MyBatis的驼峰命名,当然,我们也可以自定义结果映射规则来解决这一问题:
<!-- 自定义映射规则 -->
<resultMap id="myEmp" type="com.wwj.mybatis.bean.Employee">
<!-- 指定主键的封装规则 -->
<id column="id" property="id"/>
<!-- 指定非主键的封装规则 -->
<result column="last_name" property="lastName"/>
</resultMap>
<select id="getEmpById" resultMap="myEmp">
select *
from tbl_employee
where id = #{id}
</select>
首先通过resultMap自定义映射规则,其中id属性为唯一标识,可以随意命名,type为需要指定规则的Bean全类名,然后通过子标签id设置主键的封装规则,通过子标签result设置非主键的封装规则;column为数据表的列名,property为Bean的属性名,作一个一一映射,最后指定resultMap即可。
\
在自定义映射规则中,没有进行映射的其它字段,MyBatis仍然会进行默认的匹配,但推荐只要写了resultMap,就将所有的字段映射都写出来:
<!-- 自定义映射规则 -->
<resultMap id="myEmp" type="com.wwj.mybatis.bean.Employee">
<!-- 指定主键的封装规则 -->
<id column="id" property="id"/>
<!-- 指定非主键的封装规则 -->
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="email" property="email"/>
</resultMap>
\
resultMap还能够来解决联合查询带来的问题,现在新建一张部门表:
CREATE TABLE tbl_dept(
id INT(11) PRIMARY KEY AUTO_INCREMENT,
dept_name VARCHAR(255)
);
INSERT INTO tbl_dept VALUES('开发部');
INSERT INTO tbl_dept VALUES('测试部');
在员工表中新增一列作为外键关联部门表:
ALTER TABLE tbl_employee ADD COLUMN d_id INT(11);
ALTER TABLE tbl_employee ADD CONSTRAINT fk_emp_dept
FOREIGN KEY(d_id) REFERENCES tbl_dept(id);
此时创建Department类:
@Data
@ToString
public class Department {
private Integer id;
private String departmentName;
}
修改Employee类:
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
private Integer id;
private String lastName;
private char gender;
private String email;
private Department dept;
}
该如何实现在查询员工的同时查询出该员工所在的部门信息呢?
其实非常简单,首先编写接口方法:
Employee getEmpAndDept(@Param("id") Integer id);
然后编写配置:
<resultMap id="myEmpTo" type="com.wwj.mybatis.bean.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="email" property="email"/>
<!-- 使用级联属性 -->
<result column="d_id" property="dept.id"/>
<result column="dept_name" property="dept.departmentName"/>
</resultMap>
<select id="getEmpAndDept" resultMap="myEmpTo">
SELECT *
FROM tbl_employee e,
tbl_dept d
WHERE e.d_id = d.id
AND e.id = 1
</select>
通过sql的多表查询获取到两张表的所有字段,然后对这些字段做一一映射即可。
association
除了通过级联属性的方式能够将数据表的数据注入到Department属性中,我们还可以使用 association 标签来完成:
<resultMap id="myEmpTo" type="com.wwj.mybatis.bean.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="email" property="email"/>
<!-- 指定联合的Java对象 -->
<association property="dept" javaType="com.wwj.mybatis.bean.Department">
<id column="d_id" property="id"/>
<result column="dept_name" property="departmentName"/>
</association>
</resultMap>
通过association标签指定联合的Java对象,其中property为Bean中的联合对象属性名,javaType为联合对象的类型,在该标签内仍然以同样的方式进行字段和属性的映射,只不过现在映射的是Department类而已。
\
association标签还支持分步查询,我们可以先查出员工信息,再从员工信息中取出部门id,查询部门信息,编写接口方法:
Employee getEmpByIdStep(@Param("id") Integer id);
编写配置:
<resultMap id="myEmpByStep" type="com.wwj.mybatis.bean.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="email" property="email"/>
<!-- 定义联合对象的映射规则 -->
<association property="dept"
select="com.wwj.mybatis.mapper.DepartmentMapper.getDeptById"
column="d_id">
</association>
</resultMap>
<select id="getEmpByIdStep" resultMap="myEmpByStep">
SELECT *
FROM tbl_employee
WHERE id = #{id};
</select>
这里需要注意的是association标签起的是分布查询的功能,首先通过select标签能够查询出员工信息,其中就有部门的id;property属性指定需要分步查询的对象属性名,select属性指定部门信息的查询方法,column属性指定需要从员工信息中取出d_id字段用于部门查询。
\
最后编写业务代码:
Employee emp = employeeMapper.getEmpByIdStep(1);
System.out.println(emp);
执行结果:
==> Preparing: SELECT * FROM tbl_employee WHERE id = ?;
==> Parameters: 1(Integer)
<== Columns: id, last_name, gender, email, d_id
<== Row: 1, jack, 1, jack@qq.com, 1
====> Preparing: select id, dept_name departmentName from tbl_dept where id = ?
====> Parameters: 1(Integer)
<==== Columns: id, departmentName
<==== Row: 1, 开发部
<==== Total: 1
<== Total: 1
Employee(id=1, lastName=jack, gender=1, email=jack@qq.com, dept=Department(id=1, departmentName=开发部))
控制台输出两条sql,分布查询完成。
\
分布查询还支持懒加载模式,即:在查询员工信息时并不会查询部门信息,而是当需要使用部门信息时才去查询,大大提升了系统性能,避免了不必要的查询损耗,实现方式如下:
<!-- 开启懒加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 禁用全部加载,设置按需加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
只需在全局配置文件中添加这两段配置即可,其中 lazyloadingEnable 是延迟加载开关,设置为true则开启懒加载;而 aggressiveLazyLoading 的值若为true时,则任一方法的调用都会加载该对象的所有延迟加载属性,设置为false则会按需加载,需要注意的是该属性在3.4.1版本及其之前的版本默认值为true,之后的版本默认值为false,所以如果MyBatis版本大于等于3.4.1,则无需设置该属性。
此时若是没有使用到部门信息,则不会查询部门信息:
Employee emp = employeeMapper.getEmpByIdStep(1);
System.out.println(emp.getEmail());
执行结果:
==> Preparing: SELECT * FROM tbl_employee WHERE id = ?;
==> Parameters: 1(Integer)
<== Columns: id, last_name, gender, email, d_id
<== Row: 1, jack, 1, jack@qq.com, 1
<== Total: 1
jack@qq.com
collection
现在有一个需求,查询部门id为1的所有员工信息,该如何实现呢?
\
首先在Department类中添加一个集合属性用于存储员工信息:
@Data
@ToString
public class Department {
private Integer id;
private String departmentName;
private List<Employee> emps;
}
然后编写接口方法:
Department getDeptByIdQueryEmps(@Param("id") Integer id);
编写配置,此时我们需要借助collection标签完成业务:
<resultMap id="MyDept" type="com.wwj.mybatis.bean.Department">
<id column="did" property="id"/>
<result column="dept_name" property="departmentName"/>
<!-- 定义联合集合的映射规则 -->
<collection property="emps" ofType="com.wwj.mybatis.bean.Employee">
<id column="eid" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="email" property="email"/>
</collection>
</resultMap>
<select id="getDeptByIdQueryEmps" resultMap="MyDept">
select d.id did,
d.dept_name dept_name,
e.id eid,
e.last_name last_name,
e.gender gender,
e.email email
from tbl_dept d
left join tbl_employee e
on d.id = e.id
where d.id = #{id}
</select>
首先编写联合查询的sql,其次定义联合集合的映射规则,其中property属性用于指定联合集合的属性名;ofType指定集合存放的元素类型;在collection标签内需要定义集合存放的元素类型的映射规则,最后编写业务代码:
Department department = departmentMapper.getDeptByIdQueryEmps(1);
System.out.println(department);
执行结果:
Department(id=1, departmentName=开发部, emps=[Employee(id=1, lastName=jack, gender=1, email=jack@qq.com, dept=null)])
collection和association一样,也可以进行分步查询,编写接口方法:
Department getDeptByIdQueryEmpsStep(@Param("id") Integer id);
编写配置:
<resultMap id="MyDeptTo" type="com.wwj.mybatis.bean.Department">
<id column="id" property="id"/>
<result column="dept_name" property="departmentName"/>
<collection property="emps"
select="com.wwj.mybatis.mapper.EmployeeMapper.getEmpsByDeptId"
column="id">
</collection>
</resultMap>
<select id="getDeptByIdQueryEmpsStep" resultMap="MyDeptTo">
select id, dept_name departmentName
from tbl_dept
where id = #{id}
</select>
它的用法和association其实是一样的,首先通过select标签查询部门信息,并从中获取部门id,然后查询该部门下的所有员工信息,为此,collection标签在这里起分步查询的作用。其中,property用于指定需要分步查询的属性名;select属性指定需要调用哪个方法进行分步查询;column指定查询得到的部门信息中,采用哪个字段进行分步查询。
\
所以,我们需要提供一个根据部门id查询所有员工的方法:
List<Employee> getEmpsByDeptId(@Param("id") Integer id);
<select id="getEmpsByDeptId" resultType="com.wwj.mybatis.bean.Employee">
select *
from tbl_employee
where d_id = #{id}
</select>
最后编写业务代码:
Department department = departmentMapper.getDeptByIdQueryEmpsStep(1);
System.out.println(department);
执行结果:
Department(id=1, departmentName=开发部, emps=[Employee(id=1, lastName=null, gender=1, email=jack@qq.com, dept=null)])
当然了,collection标签也是支持懒加载的,同样在全局配置文件进行配置即可。
\
有时候进行分步查询,我们需要传递多个字段值,这个时候我们只需将参数值封装成Map并赋值给column属性即可,比如:
<collection property="emps"
select="com.wwj.mybatis.mapper.EmployeeMapper.getEmpsByDeptId"
column="{id=id,dept_name=dept_name}">
</collection>
其中的键为调用的分步方法所需的属性名,而值为第一次查询得到的即将用于分步查询的字段名。
discriminator
该标签用于配置鉴别器,它可以根据某列的值进行改变封装行为,比如这样的一个需求:
\
根据id查询员工,若查询得到的是女员工,则查询出部门信息;否则不查询:
<resultMap id="MyEmpDis" type="com.wwj.mybatis.bean.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="email" property="email"/>
<!-- 配置鉴别器,根据gender封装 -->
<discriminator javaType="java.lang.String" column="gender">
<!-- 为女生,查询部门信息 -->
<case value="0" resultType="com.wwj.mybatis.bean.Employee">
<association property="dept"
select="com.wwj.mybatis.mapper.DepartmentMapper.getDeptById"
column="d_id">
</association>
</case>
<!-- 为男生,不作另外处理 -->
<case value="1" resultType="com.wwj.mybatis.bean.Employee">
</case>
</discriminator>
</resultMap>
首先封装id、姓名、性别和邮箱属性,然后配置鉴别器,其column指定需要以哪一列作为鉴别依据,javaType为该列的类型,在discriminator标签中通过case子标签进行判断,value指定列的值,resultType为该条件下得到的结果类型,需要注意的是,即使case标签中没有任何内容,resultType属性也是不可缺少的。