当前位置: 首页 > news >正文

MyBatis学习 | SQL映射文件

文章目录

  • 一、简介
  • 二、insert、update和delete标签
    • 2.1 关于增删改
    • 2.2 获取自增主键的值
  • 三、参数处理
    • 3.1 获取不同形式的参数
      • 3.1.1 获取单个参数
      • 3.1.2 获取多个参数
    • 3.2 #{Key}
      • 3.2.1 #{}🆚${}
      • 3.2.2 #{}中设置参数规则
  • 四、select标签
    • 4.1 select标签的主要属性
    • 4.2 关于返回值类型
      • 4.2.1 返回值类型为List集合
      • 4.2.2 返回值类型为Map集合
    • 4.3 嵌套结果集查询
      • 4.3.1 resultMap属性和resultMap标签
      • 4.3.2 级联查询
      • 4.3.3 association标签
      • 4.3.4 collection标签
      • 4.3.5 association标签🆚collection标签


学习地址🔗

  • https://www.bilibili.com/video/BV1mW411M737
  • https://www.bilibili.com/video/BV1NE411Q7Nx
  • 官网文档

一、简介

💬概述:SQL映射文件是MyBatis的核心文件,它指导MyBatis对数据库进行CRUD操作,有着重要意义

🔑SQL映射文件中的重要标签

标签名解释
cache命名空间的二级缓存配置
cache-ref其他命名空间缓存配置的引用
resultMap自定义结果集映射
sql抽取可重用的语句块
insert映射插入(INSERT)语句
update映射更新(UPDATE)语句
delete映射删除(DELETE)语句
select映射查询(SELECT)语句

💡 还有一个parameterMap标签,但已经废弃

🔑根标签<mapper>

  • 概述:SQL映射文件的根标签是<mapper>——映射,表示映射文件中每一条SQL语句都需要与对应的持久层接口方法建立映射关系,不然没有意义
  • 唯一属性——namespace:名称空间,<mapper>的唯一属性,也是必须要设置的属性,因为它是SQL映射文件的唯一标识,属性值为对应的持久层接口的全类名,将映射文件与持久层接口建立映射关系

二、insert、update和delete标签

2.1 关于增删改

💬概述:在SQl映射文件中使用<insert><update><delete>三个SQL语句标签可以实现增删改操作

🔑增删改标签的主要属性

属性名解释
id标签的唯一标识,对应持久层接口的方法名,必须添加
parameterType参数类型,对应该SQL语句标签对应的持久层接口方法中的形参类型,只适用于单个形参,可以不添加
resultType返回值类型,对应该SQL语句标签对应的持久层接口方法中的形参类型,可以不添加

🔑实现增删改操作的注意事项

  • 在持久层接口中创建对应增删改方法时,方法返回值类型可以是void、boolean、int、long以及它们对应的包装类
  • 实现增删改操作时,需要修改数据库表数据,因此需要对当前事务进行提交,而MyBatis中通过SqlSessionFactory.openSession()获取sqlSession对象时,默认不会自动提交,实现提交有两种方式
    ① 设置自动提交:在openSession()方法中,带上true参数,即openSession(true),此时获取的sqlSession对象在操作完增删改后就回自动提交事务,不用手动提交
    ② 手动提交:使用openSession()方法直接获取sqlSession对象,在实现增删改操作之后,再通过sqlSession.commit()方法手动提交事务

🔑测试

  • 持久层接口方法

    public interface EmployeeDao {
        /**
         * 添加一条员工记录
         * @param newEmployee 新的员工对象
         */
        void addEmp(Employee newEmployee);
    
        /**
         * 更新员工信息
         * @param updatedEmp 被更新的员工对象
         */
        void updateEmpInfo(Employee updatedEmp);
    
        /**
         * 根据id删除员工信息
         * @param empId 员工id
         */
        void deleteEmpById(Integer empId);
    }
    
  • 对应映射文件中的SQL语句

    <?xml version="1.0" encoding="utf8"?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="com.key.mybatis.dao.EmployeeDao">
    
        <!-- 添加一条员工记录 -->
        <insert id="addEmp" parameterType="com.key.mybatis.entity.Employee">
            insert into `employee`(`emp_name`, `gender`, `email`, `dept_id`)
            values(#{empName}, #{gender}, #{email}, 1);
        </insert>
    
        <!-- 更新员工信息 -->
        <update id="updateEmpInfo" parameterType="com.key.mybatis.entity.Employee">
            update `employee`
            set `emp_name` = #{empName}, `gender` = #{gender}, `email` = #{email}
            where emp_id = #{empId};
        </update>
    
        <!-- 根据id删除员工信息 -->
        <delete id="deleteEmpById" parameterType="int">
            delete
            from `employee`
            where emp_id = #{empId};
        </delete>
    </mapper>
    
  • 测试类

    public class EmployeeTest {
    
        /**
         * 测试插入
         */
        @Test
        public void testInsert() {
            // 获取SqlSession对象
            SqlSession sqlSession = MyBatisUtil.getSqlSession();
    
            // 根据sqlSession对象获取mapper对象
            EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class);
    
            // 创建一个新的Employee对象
            Employee newEmp = new Employee(null, "周星驰",
                    "男", "zhouxingchi@qq.com");
    
            // 调用接口方法,传入新的员工对象
            mapper.addEmp(newEmp);
    
            // 提交事务,一定要提交事务,不然插入操作失败
            sqlSession.commit();
    
            // 最后关闭sqlSession对象
            sqlSession.close();
        }
    
        /**
         * 测试更新
         */
        @Test
        public void testUpdate() {
            // 获取SqlSession对象
            SqlSession sqlSession = MyBatisUtil.getSqlSession();
    
            // 根据sqlSession对象获取mapper对象
            EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class);
    
            // 创建需要更新的员工对象
            Employee updatedEmp = new Employee(2, "周润发",
                    "男", "zhourunfa@gmail.com");
    
            // 调用接口方法,传入更新的员工对象
            mapper.updateEmpInfo(updatedEmp);
    
            // 提交事务
            sqlSession.commit();
    
            // 关闭sqlSession
            sqlSession.close();
        }
    
        /**
         * 测试删除
         */
        @Test
        public void testDelete() {
            // 获取SqlSession对象
            SqlSession sqlSession = MyBatisUtil.getSqlSession();
    
            // 根据sqlSession对象获取mapper对象
            EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class);
    
            // 调用接口方法
            mapper.deleteEmpById(4);
    
            // 提交事务
            sqlSession.commit();
    
            // 关闭sqlSession
            sqlSession.close();
        }
    }
    

2.2 获取自增主键的值

💬概述:在实现添加操作时,如果数据库表中的主键字段是自增的,则在创建对应的JavaBean时,会将主键成员变量设置null后再插入到数据表中,但插入后JavaBean中的主键成员变量还是null,此时就需要在<insert>标签中设置相关属性获取自增主键的值

🔑关于获取自增主键值的几个相关属性

属性名属性值解释
useGeneratedKeystrue/false,默认是false设置使用主键策略,即获取自增主键值;设置为true表示使用主键策略,必须添加
keyPropertyJavaBean中自增主键对应的成员变量名将获取的自增主键值赋值给JavaBean中对应的成员变量,必须添加
keyColumn数据库表中对应的自增主键字段名标识出数据库表中对应的自增主键字段,可以不添加

💡 MyBatis底层中获取自增主键的值使用的也是原生JDBC中statement对象,即通过statement.getGeneratedKeys()方法获取

🔑获取自增主键值方式:直接在<insert>标签中添加useGeneratedKeys="true"keyProperty="empId"两个属性即可

<!-- 添加一条员工记录 -->
<insert id="addEmp" parameterType="com.key.mybatis.entity.Employee"
        useGeneratedKeys="true" keyProperty="empId" keyColumn="emp_id">
    insert into `employee`(`emp_name`, `gender`, `email`, `dept_id`)
    values(#{empName}, #{gender}, #{email}, 1);
</insert>

三、参数处理

3.1 获取不同形式的参数

💡 在持久层接口的方法中可以设置多个多种不同类型的形参,因此在SQL映射文件中获取不同参数时有不同的处理方式

3.1.1 获取单个参数

🔑传入的形参是普通类型:对于单个普通类型的形参,如String nameMyBatis不会对其做特殊处理,因此在映射文件中通过#{Key}获取对应参数值时,#{Key}中的键Key可以是任意字符串,不一定与方法形参名一致

🔑测试

// 持久层接口方法
Employee getEmpById(Integer empId);
<!-- 
	* 对应映射文件中的SQL标签 
		- #{empId}用于获取形参的参数值时
		- #{empId}中的键Key可以任意,#{id}、#{aaa}都可
-->
<select id="getEmpById" resultType="com.key.mybatis.entity.Employee" parameterType="int">
    select *
    from `employee`
    where emp_id = #{empId};
</select>

3.1.2 获取多个参数

🔑传入的形参都是普通类型:持久层接口中传入多个普通类型的形参,MyBatis会将所有形参封装成一个Map集合Map集合中的键Key就是#{Key}中的Key,值Value就是参数值,因此通过#{Key}获取每一个参数值时,#{Key}中的键Key就不能是任意的,键Key有下列两种情况

  • 没有对每一个形参进行命名

    • 键的选取:只能使用MyBatis为每一个形参设置的默认键

    • 默认键:[arg0…argN-1] 或 [param1…paramN](N表示有N个形参)

    • 测试

      <!--
      	* 没有对形参命名时,通过默认键获取参数
       		- 获取第一个参数:#{arg0}或#{param1}
      		- 获取第二个参数:#{arg1}或#{param2}
      -->
      <select id="getEmpByIdAndName" resultType="com.key.mybatis.entity.Employee">
          select *
          from `employee`
          where `emp_id` = #{arg0} and
          `emp_name` = #{param2};
      </select>
      
  • 对每一个形参进行命名⭐

    • 键的选取:可以使用默认的[param1…paramN](不能使用[arg0…argN-1]),也可以使用自定义的参数名

    • 对形参进行命名的方式:在持久层接口对应方法中的每一个形参上添加@Param("自定义参数名")注解,属性值就是为对应参数自定义的名称(一般命名为与形参名一样)

    • 测试

      // 根据id和姓名获取员工信息,并为第一个形参进行命名
      Employee getEmpByIdAndName(@Param("empId") Integer empId, String empName);
      
      <!--
      	* 对形参命名时,可以通过[paramN]获取参数,也可以通过自定义参数名获取
       		- 获取第一个参数:#{param1}或#{empId}
      		- 获取第二个参数:#{arg1}或#{param2}
      -->
      <select id="getEmpByIdAndName" resultType="com.key.mybatis.entity.Employee">
          select *
          from `employee`
          where `emp_id` = #{empId} and
          `emp_name` = #{param2};
      </select>
      

🔑传入单个特殊类型形参

  • 将需要获取的参数封装后传入:在持久层接口方法中可以将多个需要获取的参数封装后再传入(封装后就变为单个特殊类型的形参

    • 一般封装后的形参类型下列三种情况

      形参类型使用方式获取参数值时的键Key适用场景
      POJO(普通JavaBean)直接在持久层接口对应方法上传入JavaBean类型的形参参数对应的JavaBean属性名获取的多个参数正好与JavaBean中成员变量一一对应
      Map<String,Object>类型持久层接口方法形参设置为Map<String,Object>类型,传入实参时,需要先创建一个Map<String,Object>集合,在集合中添加参数和参数值(键值对形式),然后传入方法中自定义Map集合中的键Key多个参数与JavaBean不是一一对应,且不经常使用
      DTO(数据传输对象)自定义一个DTO类型的对象,作为持久层接口方法的形参类型参数对应的DTO对象的属性名多个参数与JavaBean不是一一对应,且经常使用

      ❓ 关于DTO:Data Transfer Object,数据传输对象,也是一个POJO,与数据库表中的每一个字段不是一一对应关系,一般会有增加或删减

      ❓ 关于对象类型的形参

      • 不对形参进行命名:如果对于单个POJO或DTO形参,不对其自定义参数名时,键Key直接就是对象的属性名,不能使用【对象.属性名】的形式
      • 对形参进行命名:如果对POJO或DTO形参进行命名,则键Key就不能是简单的对象属性名,需要使用【对象.属性名】的形式,而【对象.属性名】中的【对象】可以是【paramN】,也可以是自定义的形参名
    • 测试

      ① 测试POJO

      // 持久层接口方法,不对形参进行命名
      Employee getEmpByEmpPo(Employee empPo);
      
      <!-- 
      	将多个参数封装成POJO后传入,获取参数时通过POJO的属性名作为键Key获取即可 
      -->
      <select id="getEmpByEmpPo" resultType="com.key.mybatis.entity.Employee">
          select *
          from `employee`
          where `emp_id` = #{empId} and
          	  `emp_name` = #{empName} and
                `gender` = #{gender} and
                `email` = #{email};
      </select>
      

      ② 测试Map<String,Object>集合

      // 根据Map集合获取参数
      Employee getEmpByMap(Map<String, Object> map);
      
      <!-- 
      	传入形参是Map集合类型,则获取参数的键Key就是Map集合的键 
      -->
      <select id="getEmpByMap" resultType="com.key.mybatis.entity.Employee">
          select *
          from `employee`
          where `emp_id` = #{id} and
          `emp_name` = #{name};
      </select>
      
  • 传入形参是集合或数组类型:持久层接口方法中可以传入单个集合或数组类型的形参,即Collection类型或Array类型,包括ListSet,MyBatis也会将集合类型或数组的形参封装成一个Map集合

    • Map集合的键Key选值如下

      形参类型获取参数值的键Key
      Collection(包括List、Set)collection[i](Collection类型小写)
      Listcollection[i]或list[i](List类型小写)
      Array(数组)array[i](Array类型小写)

      💡 集合或数组类型获取参数时,需要根据参数在集合中的索引值i来获取,不能只写collection

    • 测试

      // 传入形参是一个id集合
      Employee getEmpByIdList(List<Integer> idList);
      
      <!-- 
      	传入形参是List集合类型,获取的键Key为collection[i]或list[i] 
      -->
      <select id="getEmpByIdList" resultType="com.key.mybatis.entity.Employee">
          select *
          from `employee`
          where `emp_id` = #{list[0]};
      </select>
      

      💡 如果集合中元素类型为对象类型,则获取集合中某个元素的属性时,可以通过【对象.属性】形式获取,只是这里的【对象】需要先根据集合索引来获取,如获取第二个元素(对象)的name属性值——list[1].name

🔑传入形参中既有普通类型又有对象类型:在持久层接口方法中可以添加不同类型的形参,当添加一个普通类型形参和一个对象类型形参,在SQL标签中要获取的参数需要从对象类型的形参中获取,此时MyBatis同样会将两个形参封装成一个Map集合

  • 集合的键Key有下列情况

    • 对于普通类型的形参:普通类型的形参适用于MyBatis设置的默认的键Key——[arg0…argN-1] 或 [param1…paramN];如果不使用默认的键Key,同样可以对每一个普通类型形参进行命名,此时键Key既可以时默认的(不能使用[arg0…argN-1]),也可以是自定的参数名
    • 对于对象类型的形参:需要获取的参数得从对象形参的属性中获取,因此键Key必须是【对象.属性名】的形式,而【对象.属性名】中的对象相当于一个普通类型的形参,既可以是默认的[param1…paramN](不能使用[arg0…argN-1]),也可以使用自己命名的参数名(命名方式同普通类形参)
  • 测试

    // 传入的形参既有普通类型,又有对象类型,没有对形参进行命名
    Employee getEmpByNameAndUserId(String empName, User user);
    
    <!--  
    	* 传入形参既有普通类型,又有对象类型,获取两种形式的参数时键Key有所不同(没有命名形参的情况下)
    		- 获取普通类型的键Key:#{arg0}或#{param1}
    		- 获取对象类型封装的参数时的键Key:#{arg1.userid}或#{param2.userid}
    -->
    <select id="getEmpByNameAndUserId" resultType="com.key.mybatis.entity.Employee">
        select *
        from `employee`
        where `emp_name` = #{param1} and
        `emp_id` = #{arg1.userid};
    </select>
    

3.2 #{Key}

3.2.1 #{}🆚${}

🔑共同点

  • 都可以获取由持久层接口方法传过来的参数值
  • 获取参数值的方式一样,都是在{}中添加参数值对应的键Key即可

🔑区别

获取参数两种方式在SQL语句中设置参数值的方式适用场景适用场景例子
#{Key}#{Key}通过预编译的方式将参数值设置到SQL语句中,而SQL字符串中对应参数值的位置用占位符?来拼接,而不是直接将参数值拼接到SQL中,相当于JDBC中的PreparedStatement对象,可以防止SQL注入原生JDBC支持使用占位符?的地方,一般都使用#{Key}WHERE语句中获取参数值
${Key}${Key}是直接将获取的参数值拼接到SQL语句中,不进行预编译,因此会有SQL注入的安全问题不能使用#{Key}获取参数的情况,即JDBC不支持占位符地方就可以使用${Key}获取参数值①获取数据库表名;②获取排序规则的ASEDESE

3.2.2 #{}中设置参数规则

💬概述#{}中除了可以添加键Key来获取对应参数值之外,还可以设置获取参数时的其他规则

🔑可以设置的主要规则

  • javaType:设置JavaBean的属性类型
  • jdbcType:设置数据库表中字段的类型
  • mode:设置存储过程
  • resultMap:设置结果集
  • jdbcName:设置数据库表字段名
  • typeHandler:设置类型处理器

四、select标签

4.1 select标签的主要属性

属性名解释是否必须设置
id<select>标签的唯一标识,对应持久层接口方法名,与方法进行绑定
resultType设置返回值类型
parameterType设置参数类型(仅适用单个形参)
resultMap设置结果集中数据库表的字段名和JavaBea中属性名之间的映射规则,属性值对应<resultMap>的唯一标识id,不能与resultType一起使用

4.2 关于返回值类型

4.2.1 返回值类型为List集合

💬概述:在持久层接口方法中,可以设置返回值类型为List<T>,此时<select>标签中的resultType的属性值为集合中泛型T对应的全类名,而不是集合List的全类名

🔑测试

// 持久层接口方法
List<Employee> getAllEmployees();
<!-- 返回值类型设置为List中的泛型 -->
<select id="getAllEmployees" resultType="com.key.mybatis.entity.Employee">
	select *
    from `employee`;
</select>

4.2.2 返回值类型为Map集合

💬概述:持久层接口方法的返回值类型可以设置为Map<K,V>,此时<select>标签中的resultType的属性值就为Map类型的全类名,但MyBatis已经对常用的Java类型设置过别名,因此属性名直接写类型小写map即可

🔑对于查询一条与多条记录,返回Map集合时的封装规则不同

  • 查询返回一条记录:返回一条记录时,相当于返回一个结果集对应的JavaBean,此时返回值Map<K,V>中的键值对分别对应JavaBean的属性名和属性值,一般泛型设置为Map<String,Object>

    // 持久层接口方法
    Map<String, Object> getEmpReturnMap(Integer empId);
    
    <!-- 返回值类型设置为map -->
    <select id="getEmpReturnMap" resultType="map">
    	select *
        from `employee`
        where `emp_id` = #{empId};
    </select>
    
  • 查询返回多条记录:返回多条记录时,相当于返回多个JavaBean,此时需要将每一个JavaBean作为Map集合的值Value来返回,而键Key需要自定义为JavaBean的某一个属性名(如Map<String, Employee>),将该属性与JavaBean一一对应起来

    • 自定义键Key的方式:在持久层接口对应的方法上添加@MapKey("JavaBean中某一个属性名")注解,注解中添加的属性值就是自定义的JavaBean的某一个属性名

    • 测试

      // 持久层接口方法,添加@MapKey注解,设置键Key为员工id
      @MapKey("empId")
      Map<Integer, Employee> getAllEesReturnMap();
      
      <!-- 返回值类型设置为map -->
      <select id="getAllEesReturnMap" resultType="map">
      	select *
          from `employee`;
      </select>
      

4.3 嵌套结果集查询

4.3.1 resultMap属性和resultMap标签

🔑resultMap属性

  • 作用:<select>标签的属性,用来标识结果集所使用的映射规则,而具体的映射规则需要使用<resultMap>标签来设置
  • 属性值:resultMap的属性值对应<resultMap>标签的唯一标识id,表示将<resultMap>标签中设置的映射规则应用到<select>标签的结果集中

🔑<resultMap>标签

  • 作用:设置查询结果集与JavaBean的映射规则

    ❓ 关于结果集的映射规则

    • 使用<resultMap>设置的映射规则是作用于查询出来的结果集(一张不存在的表)与某一个JavaBean之间,不是将数据库中某张表与某一个JavaBean建立映射关系
    • 查询出来的结果集与该JavaBean可以是没有关系的,也可以是相对应的,如在不取别名的情况下查询某一个员工的信息,则查询结果集中每一列与员工表每一列都相等,因此员工表对应的JavaBean就与结果集相对应

    ❓ 解决表字段名与JavaBean字段名不一致的方法

    ① 在SQL语句中为每一个字段设置别名,别名与JavaBean的属性名保持一致(比较麻烦)
    ② 在全局配置文件中通过<setting>标签开启驼峰命名映射(只能将带下划线的_表字段名转成对应的使用驼峰命名JavaBean属性名)
    ③ 使用<resultMap>标签自定义映射规则,适用于任何情况

  • <resultMap>标签的两个属性

    ① id:标签的唯一标识,属性值对应<select>标签的resultMap属性值
    ② type:标识映射规则中的JavaBean,属性值为JavaBean的全类名

  • 主要的子标签

    标签名作用主要属性是否必须添加
    <id>设置主键列名对应JavaBean属性的映射规则①column:对应结果集中的主键列名; ②property:对应JavaBean中的属性名
    <result>设置普通列名与对应JavaBean属性的映射规则同上

    ❓ 关于两个子标签

    • <id><result>的作用其实是一样的,只是使用<id>标签时,MyBatis底层会对相应的列做特殊处理
    • 两个子标签都不是必须要添加的,但不添加时就不能保证结果集与JavaBean的映射关系
  • 测试

    <!-- 设置映射规则 -->
    <resultMap id="myEmp" type="com.key.mybatis.entity.Employee">
    	<!-- 设置主键列名的映射规则 -->
        <id column="emp_id" property="empId"/>
        <!-- 设置其他列名映射规则 -->
        <result column="emp_name" property="empName"/>
        <result column="gender" property="gender"/>
        <result column="email" property="email"/>
    </resultMap>
    
    <!-- 使用自定义的映射规则处理结果集 -->
    <select id="getEmpById" resultMap="myEmp">
    	select *
        from `employee`
        where `emp_id` = #{emp};
    </select>
    

4.3.2 级联查询

🔑创建既有级联关系的表及其对应的JavaBean

  • 创建部门表department,员工表中添加部门id字段,作为外键约束

    /*创建部门表*/
    drop table if exists `department`;
    create table `department` (
    	`dept_id` int unsigned not null unique auto_increment comment '部门id - 主键',
        `dept_name` varchar(20) not null comment '部门名字',
        primary key(`dept_id`)
    ) engine = innodb default character = utf8 comment = '部门表';
    
    /*员工表中添加部门id*/
    alter table `employee`
    add column `dept_id` int unsigned not null comment '员工所在部门id - 外键';
    
    /*部门id设置为外键约束*/
    alter table `employee`
    add constraint `fk_emp_dept`
    foreign key(`dept_id`) references `department`(`dept_id`);
    
  • 创建部门表对应JavaBean——Department

    public class Department {
    
        private Integer deptId;
        private String deptName;
    
        // code...
    }
    
  • Employee类中添加对象类型属性(级联属性)——Department myDept(注意添加的不是部门的id,是部门对象)

    public class Employee {
    
        private Integer empId;
        private String empName;
        private String gender;
        private String email;
        // myDept为级联属性
        private Department myDept;
    
    	// code...
    }
    

🔑设置级联查询的结果集映射规则:在级联查询中,需要使用<resultMap>标签设置级联查询结果集的映射规则(封装规则),保证级联查询的结果,在子标签<result>中通过【对象.属性】的形式可以对级联属性进行赋值,此时【对象.属性】中的【对象】为JavaBean(员工类)中的对象类型属性名(部门属性)

<!-- 设置映射规则 -->
<resultMap id="cascadeQuery" type="com.key.mybatis.entity.Employee">
    <!-- 设置主键映射规则 -->
    <id column="emp_id" property="empId"/>
    <!-- 设置其他属性的映射规则(这里省略) -->
    
    <!-- 设置级联属性的映射规则1.0 
		  - myDept是Employee类中的属性名
		  - deptId和deptName都是Department类中的属性名
	-->
    <result column="dept_id" property="myDept.deptId"/>
    <result column="dept_name" property="myDept.deptName"/>
</resultMap>

❓ 为什么不使用resultType:级联查询时,在<select>只能使用resultMap属性设置结果集,不能简单使用resultType设置返回值类型,因为resultType属性只能设置查询结果集的类型,不能设置结果集中的对象类型属性,因此调用接口方法返回的对象中,其对象类型的属性值肯定是null

🔑测试:根据员工id查询某一位员工的全部信息及其所在部门的id和部门名(连接查询)

  • SQL语句

    select e.`emp_id`, e.`emp_name`, e.`gender`, e.`email`, e.`dept_id`, d.`dept_name`
    from `employee` e, `department` d
    where e.`dept_id` = d.`dept_id` and
          e.`emp_id` = #{empId};
    
  • 员工持久层接口中的查询方法

    Employee getEmpAndDeptById(Integer empId);
    
  • 员工映射文件

    <!-- 设置映射规则 -->
    <resultMap id="cascadeQuery" type="com.key.mybatis.entity.Employee">
        <!-- 设置主键映射规则 -->
        <id column="emp_id" property="empId"/>
        <!-- 设置其他属性的映射规则 -->
        <result column="emp_name" property="empName"/>
        <result column="gender" property="gender"/>
        <result column="email" property="email"/>
    
        <!-- 设置级联属性的映射规则1.0 -->
        <result column="dept_id" property="myDept.deptId"/>
        <result column="dept_name" property="myDept.deptName"/>
    </resultMap>
    
    <!-- 级联查询 -->
    <select id="getEmpAndDeptById" resultMap="cascadeQuery">
        select e.`emp_id`, e.`emp_name`, e.`gender`, e.`email`, e.`dept_id`, d.`dept_name`
        from `employee` e, `department` d
        where e.`dept_id` = d.`dept_id` and
        e.`emp_id` = #{empId};
    </select>
    
  • 测试方法

    @Test
    public void testCascadeQuery() {
        // 获取SqlSession对象
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
    
        // 获取mapper对象
        EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class);
    
        // 调用方法
        Employee emp = mapper.getEmpAndDeptById(2);
    
        System.out.println("员工对象 --> " + emp);
        System.out.println("员工部门 --> " + emp.getMyDept());
    
        // 关闭
        sqlSession.close();
    }
    
  • 打印结果test-cascade-query-result

4.3.3 association标签

💬概述<resultMap>还有一个重要的子标签——<association>,它的使用与<resultMap>类似

🔑作用:可以设置结果集与被关联的JavaBean(部门类)的映射规则

🔑主要属性

① property:属性值为<resultMap>中设置的JavaBean(员工类)中的对象类型的属性名(部门属性),必须设置
② javaType:属性值为对象类型属性的类型全类名(部门类),即被关联的JavaBean全类名,必须设置

🔑<association>的子标签<association>中的子标签中也有<id><result>,用于设置结果集中的列名与被关联JavaBean的属性名的映射规则,用法与<resultMap>的一样

🔑使用1.0——简单的级联查询:在<resultMap>标签中添加<association>,然后添加以上两个属性和子标签

<!-- 设置映射规则 -->
<resultMap id="cascadeQuery" type="com.key.mybatis.entity.Employee">
    <!-- 设置主键映射规则 -->
    <id column="emp_id" property="empId"/>
    <!-- 设置其他属性的映射规则(这里省略) -->

	<!-- 设置级联属性的映射规则2.0 
	 - myDept是员工类中的属性名
	 - javaType属性值为被关联的JavaBean(部门类)的全类名
	-->
    <association property="myDept" javaType="com.key.mybatis.entity.Department">
        <!-- 设置被关联的JavaBean的映射规则 -->
        <id column="dept_id" property="deptId"/>
        <result column="dept_name" property="deptName"/>
    </association>
</resultMap>

❓ 关于主键映射规则设置

  • 使用<association>设置级联属性映射规则时,也能通过<id>标签设置主键,而外部的父标签<resultMap>中也有一个<id>设置主键,两个<id>中的column属性值不能一样
  • 如果员工表和部门表的主键字段名一样,都是id,则在查询时需要通过取别名的方式区分两个主键,保证两个<id>标签中的column属性值不一样

🔑使用2.0——分步查询

  • ❓什么是分步查询

    • 将级联查询分步进行,以员工部门为例,分步查询就是先根据员工emp_id在员工表中查询出对应的员工信息,查询结果中会有一列是员工所在部门的dept_id,根据这个查询出来的dept_id,再去部门表查询对应的部门信息,此时就能将emp_id所对应的员工信息以及员工所在部门信息都查询出来
    • 使用<association>实现分步查询时也是遵循以上原理,先根据传入的empId查出对应的员工对象,再根据查询的结果中的deptId查询对应的部门对象,因此会执行两个持久层接口方法(两个SQL标签/两条SQL语句)
  • <association>标签中关于分步查询的三个重要属性

    属性名属性值解释是否必须添加
    property<resultMap>中设置的JavaBean(员工类)中的对象类型的属性名(部门属性)与级联查询的使用方式一样,都是用来标识级联属性
    select被关联JavaBean的持久层接口的方法,需要在方法前写上方法所在类的全类名标识被关联JavaBean的方法,通过该方法获取的对象(部门对象)赋值给property所标识的级联属性
    column结果集中某一列的列名标识查询出来的结果集中某一列的列名,select中标识的方法会根据该结果集列的值,来查询对应的被关联对象,最后再赋值给property的级联属性
  • 测试

    • 创建被关联JavaBean的持久层接口及其映射文件,并添加相应方法

      // 部门持久层接口
      public interface DepartmentDao {
      
          /**
           * 根据id查询部门信息
           */
          Department getDeptById(Integer deptId);
      }
      
      <?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.key.mybatis.dao.DepartmentDao">
      
          <!-- 根据id查询部门id -->
          <select id="getDeptById" resultType="com.key.mybatis.entity.Department">
              select *
              from `department`
              where `dept_id` = #{deptId};
          </select>
      </mapper>
      
    • 员工持久层接口中对应方法

      Employee getEmpAndDeptByIdStep(Integer empId);
      
    • 员工映射文件中

      <!-- 分步查询的映射规则 -->
      <resultMap id="stepQuery" type="com.key.mybatis.entity.Employee">
          <!-- 主键 -->
          <id column="emp_id" property="empId"/>
          <!-- 其他属性 -->
          <result column="emp_name" property="empName"/>
          <result column="gender" property="gender"/>
          <result column="email" property="email"/>
      
          <!-- association实现分步查询 
      		 - property属性值(myDept):员工类中的部门属性
      		 - select属性值:部门持久层接口全类名.对应方法
      		 - column属性值(dept_id):查询出来的结果集中部门id的列名
      	-->
          <association property="myDept"
                       select="com.key.mybatis.dao.DepartmentDao.getDeptById"
                       column="dept_id"/>
      </resultMap>
      
      <!-- 根据id查询员工信息 -->
      <select id="getEmpAndDeptByIdStep" resultMap="stepQuery">
          select *
          from `employee`
          where `emp_id` = #{empId};
      </select>
      
    • 测试方法

      @Test
      public void testStepQuery() {
          // 获取SqlSession
          SqlSession sqlSession = MyBatisUtil.getSqlSession();
      
          // 获取mapper
          EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class);
      
          // 调用分步查询的方法
          Employee emp = mapper.getEmpAndDeptByIdStep(3);
      
          System.out.println("员工对象 --> " + emp);
          System.out.println("员工部门 --> " + emp.getMyDept());
      
          // 关闭
          sqlSession.close();
      }
      
    • 打印结果test-step-query-result

🔑使用3.0——延迟加载

  • ❓什么是延迟加载:延迟加载也叫懒加载、按需加载,即在分步查询的基础上,按需要获取另一个结果集对象

    💡 还是以上面为例,如果开启了延迟加载,则当我们只获取员工对象中的基本信息(姓名、性别等),不获取其部门信息时,MyBatis就只会加载第一条SQL语句(在控制台只有一条SQL语句被打印),只有当我们获取部门信息时,MyBatis才会加载第二条SQL语句,获取出对应的部门信息

  • 开启延迟加载:在MyBatis全局配置文件中的<setting>标签中设置下列两个参数

    参数设置参数值解释
    lazyLoadingEnabledtrue开启全局的延迟加载,默认是参数值是false
    aggressiveLazyLoadingfalse侵入延迟加载,默认参数值是true
    <!-- 设置MyBatis运行时的参数 -->
    <settings>
        <!-- 开启延迟加载 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
    
  • 测试1.0——只获取员工的姓名

    • 测试方法

      // 调用分步查询的方法
      Employee emp = mapper.getEmpAndDeptByIdStep(3);
      System.out.println("员工姓名 --> " + emp.getEmpName());
      
    • 打印结果test-lazy-loading-result01

  • 测试2.0——直接打印员工对象emp,获取员工全部信息

    • 测试方法

      // 调用分步查询的方法
      Employee emp = mapper.getEmpAndDeptByIdStep(3);
      System.out.println("员工全部信息 --> " + emp);
      
    • 打印结果test-lazy-loading-result02

  • 测试3.0——获取员工信息及其部门信息

    • 测试方法

      // 调用分步查询的方法
      Employee emp = mapper.getEmpAndDeptByIdStep(3);
      System.out.println("员工全部信息 --> " + emp);
      System.out.println("员工部门 --> " + emp.getMyDept());
      
    • 打印结果test-lazy-loading-result03

4.3.4 collection标签

💬概述<resultMap>另一个实现嵌套结果集的子标签——<collection>,用法与<association><resultMap>类似

🔑作用:设置结果集与集合类型属性之间的映射规则,集合中的元素类型(即泛型)是被关联的JavaBean类型

🔑主要属性

① property:属性值为<resultMap>中设置的JavaBean(部门类)中集合类型属性名,必须设置
② ofType:属性值为集合类型属性中,集合元素类型(员工类)的全类名,即被关联JavaBean全类名,必须设置

🔑<collection>的子标签<collection>中的子标签中也有<id><result>,用于设置结果集与集合元素类型(员工类)的属性之间的映射规则,用法与<association><resultMap>一样

🔑使用1.0——简单的级联查询

  • 在部门类Department中添加一个集合类型属性,集合的泛型设置为员工类Employee,表示一个部门有多个员工

  • 测试:根据部门id查询出部门的信息及其该部门下所有员工信息(左连接查询)

    • SQL语句

      select *
      from `department` d
      left join `employee` e
      on d.`dept_id` = e.`dept_id`
      where d.`dept_id` = #{deptId};
      
    • 部门持久层接口中对应方法

      // 根据id查询部门信息及其部门下所有员工信息
      Department getDeptAndEmpListById(Integer deptId);
      
    • 部门映射文件

      <!-- 设置结果集映射规则 -->
      <resultMap id="collQuery" type="com.key.mybatis.entity.Department">
      	<!-- 设置主键 -->
          <id column="dept_id" property="deptId"/>
          <!-- 设置其他属性 -->
          <result column="dept_name" property="deptName"/>
          
          <!-- 设置集合类型属性(级联属性) -->
          <collection property="empList" ofType="com.key.mybatis.entity.Employee">
          	<!-- 主键 -->
              <id column="emp_id" property="empId"/>
              <!-- 其他属性 -->
              <result column="emp_name" property="empName"/>
              <result column="gender" property="gender"/>
              <result column="email" property="email"/>
          </collection>
      </resultMap>
      
    • 测试方法

      @Test
      public void testCollQuery() {
          // 获取SQlSession
          SqlSession sqlSession = MyBatisUtil.getSqlSession();
      
          // 获取mapper
          DepartmentDao mapper = sqlSession.getMapper(DepartmentDao.class);
      
          // 调用部门持久层接口方法
          Department dept = mapper.getDeptAndEmpListById(3);
      
          System.out.println("部门信息 --> " + dept);
          System.out.println("部门下的所有员工信息");
          dept.getEmpList().forEach(System.out :: println);
      
          // 关闭sqlSession
          sqlSession.close();
      }
      
    • 打印结果test-collection-query-result

🔑使用2.0——分步查询:<collection>实现分步查询与<association>类似

  • 实现分步查询的重要属性

    属性名属性值解释是否必须添加
    property<resultMap>中设置的JavaBean(部门类)中的集合类型的属性名(员工集合)与级联查询的使用方式一样,都是用来标识级联属性
    select被关联JavaBean的持久层接口的方法,需要在方法前写上方法所在类的全类名标识被关联JavaBean的方法,通过该方法获取的集合(员工集合)赋值给property所标识的级联属性(集合类型属性)
    column结果集中某一列的列名标识查询出来的结果集中某一列的列名,select中标识的方法会根据该结果集列的值,来查询对应的集合,最后再赋值给property的级联属性
  • 测试

    • 员工持久层接口方法

      // 根据部门id查询该部门下所有员工信息
      List<Employee> getEesByDeptId(Integer deptId);
      
    • 员工映射文件

      <!-- 根据部门id查询该部门下所有员工 -->
      <select id="getEesByDeptId" resultType="com.key.mybatis.entity.Employee">
          select *
          from `employee`
          where `dept_id` = #{deptId};
      </select>
      
    • 部门持久层接口方法

      // 分步查询
      Department getDeptAndEesByIdStep(Integer deptId);
      
    • 部门映射文件

      <!-- 设置结果集映射规则 -->
      <resultMap id="collQueryStep" type="com.key.mybatis.entity.Department">
          <!-- 主键 -->
          <id column="dept_id" property="deptId"/>
          <!-- 其他属性 -->
          <result column="dept_name" property="deptName"/>
      
          <!-- association实现分步查询 
                - property属性值(empList):部门类中的员工集合属性
                - select属性值:员工持久层接口全类名.对应方法
                - column属性值(dept_id):查询出来的结果集中部门id的列名
          -->
          <collection property="empList"
                      select="com.key.mybatis.dao.EmployeeDao.getEesByDeptId"
                      column="dept_id"/>
      </resultMap>
      
      <!-- 分步查询 -->
      <select id="getDeptAndEesByIdStep" resultMap="collQueryStep">
          select *
          from `department`
          where `dept_id` = #{deptId};
      </select>
      
    • 测试方法

      @Test
      public void testCollQueryStep() {
          // 获取SQlSession
          SqlSession sqlSession = MyBatisUtil.getSqlSession();
      
          // 获取mapper
          DepartmentDao mapper = sqlSession.getMapper(DepartmentDao.class);
      
          // 调用部门持久层接口方法
          Department dept = mapper.getDeptAndEesByIdStep(3);
      
          System.out.println("部门信息 --> " + dept);
          System.out.println("部门下的所有员工信息");
          dept.getEmpList().forEach(System.out :: println);
      
          // 关闭sqlSession
          sqlSession.close();
      }
      
    • 打印结果test-collection-step-query-result

🔑使用3.0——延迟加载:同上👆

4.3.5 association标签🆚collection标签

🔑共同点

  • 都是<resultMap>的子标签
  • 都用于在嵌套结果集查询中设置级联属性的映射规则
  • 都能实现分步查询
  • 延迟加载对它们的分步查询影响一样

🔑区别

  • <association>标签用于设置对象类型的级联属性,而<collection>标签用于设置集合类型的级联属性
  • <association>中使用javaType标识对象属性的类型,而<collection>中使用ofType标识集合元素的类型

相关文章:

  • 做网站还需要搜狗吗/搜索引擎优化的基础是什么
  • 手机app微信网站建设/培训方案
  • 网站开发网站设计/搜狗营销
  • 网站的收藏本站怎么做/微信运营方案
  • 开发网站公司排行榜/百度上如何做优化网站
  • 会计常用的三个软件/seo关键词优化排名外包
  • 高校GIS系统有何作用?
  • android面经_安卓面试题<6/30>之Intent全解析
  • 借力 StarRocks,“陆战之王“ 大润发如何在零售业数字化转型中抢占先机?
  • 疫情感染开始,大多居家办公
  • 如何克隆列表以使其在分配后不会意外更改?
  • 消息中间件(消息队列)
  • 【代码审计-2】PHP框架MVC类文件上传断点测试挖掘
  • 5G SA的网络架构和关键技术
  • 计讯物联二次供水水池泵站监测方案,从根本上保障居民饮用水安全
  • RV1126笔记六:人脸识别方案<四>
  • 艾美捷 24孔细胞划痕试验试剂盒检测原理说明
  • vue实现修改title提示框-默认样式【两种方式】