Mybatis实现联表查询(association实现多对一查询、collection实现一对多查询、级联查询、分步查询)

自定义映射

设计表示要特别注意表与表之间的关系,表与表之间的关系有:一对一、一对多、多对多

若时多对一的关系时要将关联的字段放在多的一方。

例如员工与本门的关系时多对一的关系,这时就应该把关联的字段放在员工表上

员工表

CREATE TABLE `t_emp` (
  `emp_id` int NOT NULL AUTO_INCREMENT COMMENT '员工id',
  `emp_name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '员工姓名',
  `age` int DEFAULT NULL COMMENT '员工年龄',
  `gender` bigint DEFAULT NULL COMMENT '员工性别',
  `email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '员工邮箱',
  `dept_id` int DEFAULT NULL COMMENT '部门id',
  PRIMARY KEY (`emp_id`)
) ENGINE=InnoDB AUTO_INCREMENT=36 DEFAULT CHARSET=utf8mb3;

部门表

CREATE TABLE `t_dept` (
  `dept_id` int NOT NULL COMMENT '部门id',
  `dept_name` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '部门名称',
  PRIMARY KEY (`dept_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

员工表的实体类

@Data
public class Emp {

    private Integer empId;

    private String empName;

    private Integer age;

    private String gender;
    
 	private String email;
}

部门表的实体类

@Data
public class Dept {

    private Integer deptId;

    private String deptName;

}

以上数据库的字段与对应实体类的成员变量名不一致,因此在mybatis查询数据时就会报实体类中的成员变量名在数据库表中不存在的错误或查出来的数据为null的情况,这是需要将数据库的字段与实体类的成员变量名映射起来,实现的方法有三种,分别如下

方法一:在select查询语句中使用as来取别名时数据库的字段与实体类的成员变量名映射起来,若数据库的字段与实体类的成员变量名一致,则不需要起别名

public interface EmpMapper {
/**
     * 根据id查询员工信息
     * @param empId
     * @return
     */
    Emp getEmpByEmpId(@Param("empId") Integer empId);
}

<!--Emp getEmpByEmpId(@Param("empId") Integer empId);-->
<select id="getEmpByEmpId" resultMap="Emp">
    select emp_id as empId,emp_name as empName,age,gender,email from t_emp where emp_id = #{empId}
</select>

方法二:在mybatis的核心配置文件配置如下的setting设置,此时就不需要起别名了

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    <!--
        MyBatis核心配置文件中的标签必须要按照指定的顺序配置:
        properties?,settings?,typeAliases?,typeHandlers?,
        objectFactory?,objectWrapperFactory?,reflectorFactory?,
        plugins?,environments?,databaseIdProvider?,mappers?
    -->
    <settings>
        <!--将下划线映射为驼峰-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <typeAliases>
        <package name="com.atguigu.mybatis.pojo"/>
    </typeAliases>
    <!--引入mybatis的映射文件-->
    <mappers>
        <package name="com.atguigu.mybatis.mapper"/>
    </mappers>
</configuration>
<!--Emp getEmpByEmpId(@Param("empId") Integer empId);-->
<select id="getEmpByEmpId" resultMap="empResultMap">
    select * from t_emp where emp_id = #{empId}
</select>

方法三:使用resultMap自定义映射处理(推荐使用),这就是mybatis的强大之处

 <!--
        resultMap:设置自定义的映射关系
        id:唯一标识
        type:处理映射关系的实体类的类型
        常用的标签:
        id:处理主键和实体类中属性的映射关系
        result:处理普通字段和实体类中属性的映射关系
        association:处理多对一的映射关系(处理实体类类型的属性)
        collection:处理一对多的映射关系(处理集合类型的属性)
        column:设置映射关系中的字段名,必须是sql查询出的某个字段
        property:设置映射关系中的属性的属性名,必须是处理的实体类类型中的属性名
    -->

<resultMap id="empResultMap" type="Emp">
    <id column="emp_id" property="empId"></id>
    <result column="emp_name" property="empName"></result>
    <result column="age" property="age"></result>
    <result column="gender" property="gender"></result>
</resultMap>

<!--Emp getEmpByEmpId(@Param("empId") Integer empId);-->
<select id="getEmpByEmpId" resultMap="empResultMap">
    select * from t_emp where emp_id = #{empId}
</select>

多对一的映射处理

注:(多对一)对应的是一个对象,在多的一方要有一的一个对象,(一对多)对应的是一个对集合,在一一方要有多的一个集合

对一,对应的时一个对象

对多对应一个集合

员工表的实体类,员工与部门之间的关系时多个员工在一个部门,属于多对一的关系,故在员工的实体类要有一个部门实体类的对象

@Data
public class Emp {

    private Integer empId;

    private String empName;

    private Integer age;

    private String gender;
    
 	private String email;
    
    private Dept dept;//多对一(一个部门有多个员工),员工与部门之间的关系时多个员工在一个部门,属于多对一的关系,故在员工的实体类要有一个部门实体类的对象
}

部门表的实体类

@Data
public class Dept {

    private Integer deptId;

    private String deptName;
}
方式一:使用级联的的方式

mapper

public interface EmpMapper {
/**
     * 获取员工以及所对应的部门信息
     * @param empId
     * @return
     */
    Emp getEmpAndDeptByEmpId(@Param("empId") Integer empId);
}

<resultMap id="empAndDeptResultMapOne" type="Emp">
        <id column="emp_id" property="empId"></id>
        <result column="emp_name" property="empName"></result>
        <result column="age" property="age"></result>
        <result column="gender" property="gender"></result>
        <result column="dept_id" property="dept.deptId"></result>
        <result column="dept_name" property="dept.deptName"></result>
</resultMap>

<!--Emp getEmpAndDeptByEmpId(@Param("empId") Integer empId);-->
<select id="getEmpAndDeptByEmpId" resultMap="empAndDeptResultMapOne">
    select
    t_emp.*,t_dept.*
    from t_emp
    left join t_dept
    on t_emp.dept_id = t_dept.dept_id
    where t_emp.emp_id = #{empId}
</select>
方式二:使用association来处理

mapper

public interface EmpMapper {
/**
     * 获取员工以及所对应的部门信息
     * @param empId
     * @return
     */
    Emp getEmpAndDeptByEmpId(@Param("empId") Integer empId);
}

<resultMap id="empAndDeptResultMap" type="Emp">
        <id column="emp_id" property="empId"></id>
        <result column="emp_name" property="empName"></result>
        <result column="age" property="age"></result>
        <result column="gender" property="gender"></result>
        <!--
            association:处理多对一的映射关系(处理实体类类型的属性)
            property:设置需要处理映射关系的属性的属性名
            javaType:设置要处理的属性的类型
        -->
        <association property="dept" javaType="Dept">
            <id column="dept_id" property="deptId"></id>
            <result column="dept_name" property="deptName"></result>
        </association>
    </resultMap>

<!--Emp getEmpAndDeptByEmpId(@Param("empId") Integer empId);-->
<select id="getEmpAndDeptByEmpId" resultMap="empAndDeptResultMap">
    select
    t_emp.*,t_dept.*
    from t_emp
    left join t_dept
    on t_emp.dept_id = t_dept.dept_id
    where t_emp.emp_id = #{empId}
</select>
方式三:分步查询(三张表以上的关联查询常用)

mapper

public interface EmpMapper {
      /**
     * 通过分步查询查询员工以及所对应的部门信息的第一步
     * @param empId
     * @return
     */
    Emp getEmpAndDeptByStepOne(@Param("empId") Integer empId);
}
public interface DeptMapper {

    /**
     * 通过分步查询查询员工以及所对应的部门信息的第二步
     * @return
     */
    Dept getEmpAndDeptByStepTwo(@Param("deptId") Integer deptId);
}

EmpMapper.xml

  <resultMap id="empAndDeptByStepResultMap" type="Emp">
        <id column="emp_id" property="empId"></id>
        <result column="emp_name" property="empName"></result>
        <result column="age" property="age"></result>
        <result column="gender" property="gender"></result>
       <result column="email" property="email"></result>
        <!--
            property:设置需要处理映射关系的属性的属性名
            select:设置分步查询的sql的唯一标识
            column:将查询出的某个字段作为分步查询的sql的条件
            fetchType:在开启了延迟加载的环境中,通过该属性设置当前的分步查询是否使用延迟加载
            fetchType="eager(立即加载)|  lazy(延迟加载)"
        -->
        <association property="dept" fetchType="eager"
                     select="com.example.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo"
                     column="dept_id"></association>
    </resultMap>

<!--Emp getEmpAndDeptByStepOne(@Param("empId") Integer empId);-->
    <select id="getEmpAndDeptByStepOne" resultMap="empAndDeptByStepResultMap">
        select * from t_emp where emp_id = #{empId}
    </select>

DeptMapper.xml

 <!--Dept getEmpAndDeptByStepTwo(@Param("deptId") Integer deptId);-->
    <select id="getEmpAndDeptByStepTwo" resultType="Dept">
        select * from t_dept where dept_id = #{deptId}
    </select>
分步查询的优点

分步查询的优点:可以实现延迟加载

但是必须在核心配置文件中设置全局配置信息:

lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载

aggressiveLazyLoading:当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载
此时就可以实现按需加载,获取的数据是什么,就只会执行相应的sql。此时可通过association和collection中的fetchType属性设置当前的分步查询是否使用延迟加载, fetchType=“lazy(延迟加载)|eager(立即加载)”

在mybatis核心配置文件添加延迟加载的配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

    <!--
        MyBatis核心配置文件中的标签必须要按照指定的顺序配置:
        properties?,settings?,typeAliases?,typeHandlers?,
        objectFactory?,objectWrapperFactory?,reflectorFactory?,
        plugins?,environments?,databaseIdProvider?,mappers?
    --> 
    <settings>
        <!--将下划线映射为驼峰-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!--开启延迟加载-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--按需加载-->
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>

    <typeAliases>
        <package name="com.atguigu.mybatis.pojo"/>
    </typeAliases>
    <!--引入mybatis的映射文件-->
    <mappers>
        <package name="com.atguigu.mybatis.mapper"/>
    </mappers>
</configuration>

一对多的映射处理

注:(多对一)对应的是一个对象,在多的一方要有一的一个对象,(一对多)对应的是一个对集合,在一一方要有多的一个集合

对一,对应的时一个对象

对多对应一个集合

员工表的实体类

@Data
public class Emp {

    private Integer empId;

    private String empName;

    private Integer age;

    private String gender;
    
 	private String email;
   
}

部门表的实体类,部门与员工之间的关系时一个部门有多个员工,属于一对多的关系,故在部门的实体类要有一个员工实体类的集合

@Data
public class Dept {

    private Integer deptId;

    private String deptName;
    
    private List<Emp> emps;//一对多,部门与员工之间的关系时一个部门有多个员工,属于一对多的关系,故在部门的实体类要有一个员工实体类的集合
}
方式一:使用collection

mapper

public interface DeptMapper {
    /**
     * 查询部门以及部门中的员工信息
     * @param deptId
     * @return
     */
    Dept getDeptAndEmpByDeptId(@Param("deptId") Integer deptId);
}
 <resultMap id="deptAndEmpResultMap" type="Dept">
        <id column="dept_id" property="deptId"></id>
        <result column="dept_name" property="deptName"></result>
        <!--
            ofType:设置集合类型的属性中存储的数据的类型
        -->
        <collection property="emps" ofType="Emp">
            <id column="emp_id" property="empId"></id>
            <result column="emp_name" property="empName"></result>
            <result column="age" property="age"></result>
            <result column="gender" property="gender"></result>
            <result column="email" property="email"></result>
        </collection>
  </resultMap>

<!--Dept getDeptAndEmpByDeptId(@Param("deptId") Integer deptId);-->
    <select id="getDeptAndEmpByDeptId" resultMap="deptAndEmpResultMap">
        SELECT *
        FROM t_dept
        LEFT JOIN t_emp
        ON t_dept.dept_id = t_emp.dept_id
        WHERE t_dept.dept_id = #{deptId}
    </select>
方式二:分步查询(三张表以上的关联查询常用)

mapper

public interface DeptMapper {
    /**
     * 查询部门以及部门中的员工信息
     * @param deptId
     * @return
     */
    Dept getDeptAndEmpByDeptId(@Param("deptId") Integer deptId);

    /**
     * 通过分步查询查询部门以及部门中的员工信息的第一步
     * @param deptId
     * @return
     */
    Dept getDeptAndEmpByStepOne(@Param("deptId") Integer deptId);
public interface EmpMapper {
    /**
     * 通过分步查询查询部门以及部门中的员工信息的第二步
     * @param deptId
     * @return
     */
    List<Emp> getDeptAndEmpByStepTwo(@Param("deptId") Integer deptId);

}

DeptMapper.xml

<resultMap id="deptAndEmpResultMapByStep" type="Dept">
        <id column="dept_id" property="deptId"></id>
        <result column="dept_name" property="deptName"></result>
        <collection property="emps"
                    select="com.atguigu.mybatis.mapper.EmpMapper.getDeptAndEmpByStepTwo"
                    column="dept_id"></collection>
 </resultMap>

    <!--Dept getDeptAndEmpByStepOne(@Param("deptId") Integer deptId);-->
    <select id="getDeptAndEmpByStepOne" resultMap="deptAndEmpResultMapByStep">
        select * from t_dept where dept_id = #{deptId}
    </select>

EmpMapper.xml

<!--List<Emp> getDeptAndEmpByStepTwo(@Param("deptId") Integer deptId);-->
    <select id="getDeptAndEmpByStepTwo" resultType="Emp">
        select * from t_emp where dept_id = #{deptId}
    </select>