一、级联查询的前提
要实现级联查询必须以内嵌对象的方式进行关联,而不能仅关联主键,仅关联主键是无法做到级联查询的,但我们在开发中往往采用的就是仅关联主键,也就是说级联查询在开发中其实用到的并不多。
public class Lock {
private Integer id;
private String lockName;
private Key key;//内嵌对象的方式关联对象
}
二、一对一的级联查询
以Key和Lock为例,Lock端维护关系,在一对一的关系中,使用级联查询时要注意最好不要两端同时维护关联关系,否则会出现死循环等问题
。
1、配置版
KeyMapper.xml配置:如果表字段和属性名不一致则需要给查询字段起别名,否则无法将查询字段的值反射注入至对象属性中,若一致则可以直接使用*
<?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.bdm.mappers.KeyMapper">
<select id="getById" parameterType="Integer" resultType="com.bdm.entities.Key">
SELECT k.id,k.key_name keyName
FROM `key` k WHERE k.id = #{id}
</select>
</mapper>
LockMapper.xml配置方式一:ResultMap + 连接查询
<?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.bdm.mappers.LockMapper">
<resultMap type="com.bdm.entities.Lock" id="lockResultMap">
<id property="id" column="id" />
<result property="lockName" column="lock_name" />
<association property="key" javaType="com.bdm.entities.Key" column="key_id">
<id property="id" column="id" />
<result property="keyName" column="key_name" />
</association>
</resultMap>
<select id="getById" parameterType="integer" resultMap="lockResultMap">
SELECT l.*,k.*
FROM `lock` l
JOIN `key` k on l.key_id = k.id
WHERE l.id = #{id}
</select>
</mapper>
注意
:
1️⃣有了resultMap,SQL文中的查询字段必须直接使用数据表中的字段名而不能再起和对象属性名相同的别名,否则会无法反射注入值,即此处可以使用*,因为字段和属性的映射关系已经在resultMap中定义了
2️⃣此时查询字段中要查询出所有字段,而不是只查`lock`表的字段,即查询字段不能只是l.*,应该是*或者l.*,k.*,否则lock对象中内嵌的key对象就会只有id有值,keyName是不会有值的,因为没有查
3️⃣若两端都以内嵌对象的方式维护了关联关系,则在<association>内部不要再映射对方的主键,否则会报错No typehandler found for property xxx。错误示例:但不映射又会使查询到的内嵌对象中的相关属性的值为null,所以最好的做法是只在一端维护关联关系,这样就不会有这个问题
<?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.bdm.mappers.LockMapper">
<resultMap type="com.bdm.entities.Lock" id="lockResultMap">
<id property="id" column="id" />
<result property="lockName" column="lock_name" />
<association property="key" javaType="com.bdm.entities.Key" column="key_id">
<id property="id" column="id" />
<result property="keyName" column="key_name" />
<!-- 这里不可以再进行映射:会报异常 -->
<!-- <result property="lock" column="lock_id" /> -->
</association>
</resultMap>
<select id="getById" parameterType="integer" resultMap="lockResultMap">
SELECT *
FROM `lock` l
JOIN `key` k on l.key_id = k.id
WHERE l.id = #{id}
</select>
</mapper>
LockMapper.xml配置方式二:ResultMap + select分段查询
<?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.bdm.mappers.LockMapper">
<resultMap type="com.bdm.entities.Lock" id="LockResultMap">
<id property="id" column="id" />
<result property="lockName" column="lock_name" />
<association property="key" column="key_id"
select="com.bdm.mappers.KeyMapper.getById" />
</resultMap>
<select id="getById" parameterType="Integer" resultMap="LockResultMap">
SELECT * FROM `lock` WHERE id = #{id}
</select>
</mapper>
注意
:
1️⃣这种方式其实是先从`lock`表中查询出所有字段的值,然后再根据查询出的key_id继续调用KeyMapper接口中的getById(),因此必须保证KeyMapper接口中的getById()同时存在,另外从后台打印的日志可以看出这种方式会发送了两条SQL到数据库,其实这种方式并不是连接查询,而是先查出一个表,再根据关联关系查另一个表,最后将查询的结果反射注入到对象的属性中
2️⃣使用这种方式时如果两端都采用了内嵌对象的方式维护了关联关系且两端都采用了select分段查询的方式则会出现死循环,会报java.lang.StackOverflowError错误
Tip
:
1️⃣<association>用于1对1级联查询,常用的三个属性如下
属性 | 说明 |
---|---|
property | 对象的属性名称 |
javaType | 对象属性的完整类型,建议包名 + 类名 |
column | 对应数据库表的字段名称 |
2️⃣在使用级联查询时有时我们可能并不需要使用关联对象的属性,这时还是查询出来就会浪费,因此最好采用延时加载
需在MyBatis核心配置文件中,做如下配置:
<settings>
<!-- 查询时,关闭关联对象即时加载以提高性能 -->
<setting name="lazyLoadingEnabled" value="true" />
<!-- 设置关联对象加载的形态,此处为按需加载字段(加载字段由SQL指定),不会加载关联表的所有字段,以提高性能 -->
<setting name="aggressiveLazyLoading" value="false" />
</settings>
2、注解版
public interface LockMapperAnnotaion{
@Select("select * from tbl_lock where id=#{id}")
@Results({
@Result(id=true,property="id",column="id"),
@Result(property="lockName",column="lock_name"),
@Result(property="key",column="key_id",one=@One(select="com.bdm.mappers.KeyMapper.getById"))
})
public Lock getLockById(Integer id);
}
三、一对多的级联查询
以员工(Emp)和部门(Dept)为例,关系即可以在多的一方以内嵌对象的方式维护,也可以在一的一方以内嵌对象列表的方式维护,但是表结构是一样的,都是在多的一方保存一的一方的主键,一对多的级联查询跟一对一级联查询一样也不能双向关联,否则同样会报java.lang.StackOverflowError错误。
1、一的一方以内嵌对象列表的方式维护关联关系
public class Dept {
private Integer id;
private String deptName;
private String locAdd;
private List<Emp> emps;
}
public class Emp {
private Integer id;
private String name;
}
DeptMapper.xml:使用<collection>标签通过select分段查询的方式完成一对多的级联查询
<?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.bdm.mappers.DeptMapper">
<resultMap id="DeptResultMap" type="com.bdm.entities.Dept">
<id property="id" column="id" />
<result property="deptName" column="dept_name" />
<result property="locAdd" column="loc_add" />
<!-- 此处的id其实是dept的id,相当于再通过dept的id查询与此关联的员工 -->
<collection property="emps" column="id" ofType="com.bdm.entities.Emp"
select="com.bdm.mappers.EmpMapper.getEmpByDeptId" />
</resultMap>
<select id="getById" parameterType="Integer" resultMap="DeptResultMap">
select * from tbl_dept where id = #{id}
</select>
</mapper>
EmpMapper.xml:配置文件中必须存在DeptMapper.xml中需要调用的getEmpByDeptId()方法
<?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.bdm.mappers.EmpMapper">
<resultMap id="EmpResultMap" type="com.bdm.entities.Emp">
<id property="id" column="id" />
<result property="name" column="name" />
</resultMap>
<select id="getEmpByDeptId" parameterType="Integer" resultMap="EmpResultMap">
select * from tbl_emp where dept_Id = #{deptId}
</select>
</mapper>
Tip
:像此处的getEmpByDeptId()方法如果只是在XxxMapper.xml文件中调用,则不在XxxMapper接口中声明也是可以的,这说明MyBatis在实现注册的接口时是以配置文件(其实还要加上接口中含有@Select等注解的方法)为准进行实现的,而不是以接口定义为准实现的。换句话说,MyBatis会为我们实现所有在配置文件中配置的方法以及在接口中声明时加了@Select、@Insert、@Update、@Delete等注解的方法
注解版:
public interface DeptMapperAnnotation{
@Select("select * from tbl_dept where id=#{id}")
@Results({
@Result(id=true,property="id",column="id"),
@Result(property="deptName",column="dept_name"),
@Result(property="locAdd",column="loc_add"),
@Result(property="emps",column="id",many=@Many(select="com.bdm.mappers.EmpMapper.getEmpByDeptId"))
})
public Dept getDeptById(Integer id);
}
2、多的一方以内嵌对象的方式维护关联关系
public class Dept {
private Integer id;
private String deptName;
private String locAdd;
}
public class Emp {
private Integer id;
private String name;
private Dept dept;//关联部门
}
EmpMapper.xml:
<?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.bdm.mappers.EmpMapper">
<resultMap id="EmpResultMap" type="com.bdm.entities.Emp">
<id property="id" column="id" />
<result property="name" column="name" />
<!--根据员工的部门id查询部门信息 -->
<association property="dept" column="dept_id"
select="com.bdm.mappers.DeptMapper.getById" />
</resultMap>
<select id="getById" parameterType="Integer" resultMap="EmpResultMap">
select *
from tbl_emp where id=#{id}
</select>
</mapper>
DeptMapper.xml:必须有EmpMapper.xml中调用的getById方法的定义
<?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.bdm.mappers.DeptMapper">
<resultMap id="DeptResultMap" type="com.bdm.entities.Dept">
<id property="id" column="id" />
<result property="deptName" column="dept_name" />
<result property="locAdd" column="loc_add" />
</resultMap>
<select id="getById" parameterType="Integer" resultMap="DeptResultMap">
select *
from tbl_dept where id = #{id}
</select>
</mapper>