Mybatis用了这么久,原来是这样

Mybatis用了这么久,原来原理是这样

1.关于MyBatis框架的小结

  • MyBatis框架主要的作用:可以简化数据库编程;
  • 在使用MyBatis实现数据库编程时,只需要定义每个数据操作功能的抽象方法,并配置这个抽象方法对应的SQL语句即可,当然,MyBatis框架还实现了数据库编程的其它细节,例如对查询结果的缓存等等;
  • MyBatis框架要求抽象方法都必须声明在接口中,即Mapper接口;
  • 了解使用MyBatis框架时需要添加的依赖;
  • 认识关于MyBatis框架的配置,包括db.propertiesspring-dao.xml中的配置;
  • 掌握抽象方法的声明原则:
    • 【返回值】如果需要执行的是INSERT / DELETE / UPDATE类型的操作,使用Integer作为返回值类型;如果需要执行的是SELECT类型的操作,使用期望的数据类型作为返回值类型,在查询时,如果是单表查询某数据,可以使用实体类作为返回值类型,如果是多表查询,应该先创建VO类,然后使用VO类作为返回值类型,如果查询多条数据,使用List<?>作为返回值类型;
    • 【方法名称】自定义,但是,不允许重载;
    • 【参数列表】按需设计,可以理解为SQL语句中哪些不确定的值,就可以设计出哪些参数,如果参数数据较多,可以将这些参数封装到1个对象中,特别是插入数据操作,必须使用封装的数据类型;最终,如果参数的数量超过1个,必须使用@Param注解配置每一个参数的名称,该名称将用于配置SQL语句时的#{}的占位符中;
  • 在配置XML映射文件时,必须保留顶部的XML声明与文档类型声明语句,并且,声明语句之前不可以有任何符号,包括空格、空白行;
  • 在配置XML映射文件时,根节点必须配置namespace属性,取值是对应的接口的全名;
  • 在配置XML映射文件中的SQL语句之前,必须添加匹配的<insert><delete><update><select>节点,这些节点都必须指定id属性,取值是抽象方法的名称;如果使用的是<select>节点,必须且只能配置resultTyperesultMap中的某1个属性,如果查询多条数据,也不必将类型声明为List集合类型的,只需要声明为List集合中的元素的类型即可;
  • 在配置XML映射文件中的SQL语句时,参数使用#{}格式的占位符来表示,当抽象方法的参数只有1个时,占位符中的名称可以是任意名称;当抽象方法的参数有多个时,占位符中的名称必须是抽象方法的参数之前@Param注解配置的名称;
  • 在使用占位符时,应该优先选取#{}格式的占位符,这种占位符在最终运行时,是预编译的,所以没有SQL注入的风险,且不需要关注参数的数据类型的问题;如果占位符表示的不是某个值,而是SQL语句的某个片段,可以使用${}格式的占位符,这种占位符在处理时是拼接到SQL语句中的,不是预编译的,所以,存在SQL注入的风险,并且需要自行考虑值的数据类型的问题,一般不推荐使用;
  • 在处理查询时,MyBatis框架要求列名与属性名一致,可使用的解决方案有2种:在SQL语句中自定义别名,并在<select>节点配置resultType为对应的封装结果的类型即可;在SQL语句中不需要自定义别名,另使用<resultMap>配置列名与属性名的对应,并在<select>节点配置resultMap属性,取值为<resultMap>id的值;
  • 理解实体类与VO类的定位,这2种类型的区别;
  • 如果查询结果中存在1对多的对应关系,查询到的多条结果需要封装1个非List的对象中,在配置<resultMap>时就需要使用<collection>节点来配置“多”的数据关系;
  • 什么时候需要自定义别名:
    • 【列名与属性名不同】在查询时,存在列名与属性名不一致的情况,却没有使用<resultMap>进行配置,则可以使用自定义别名使得列名与属性名保持一致;
    • 【多个列的名称相同】在关联表查询时,多张表中都有相同的字段名,会导致查询结果中出现相同的列名,可能进而导致MyBatis不知道如何封装(有多列的名称相同时,MyBatis默认取最左侧的那列的名称),就需要使用别名使得查询结果的列名都互不相同;
  • 什么时候需要使用<resultMap>
    • 【列名与属性名不同】在查询时,存在列名与属性名不一致的情况,却没有自定义别名(例如直接使用星号(*)表示查询的字段列表),则可以配置<resultMap>
    • 【存在一对多的关系】在关联表查询中,如果需要将查询到的多条结果封装在1个非List的对象中,必须配置<resultMap>
  • 在配置<resultMap>时,如果只是单表数据查询,如果列名与属性名相同,不需要通过<result>节点进行配置的;如果是关联表查询,无论列名与属性名是否相同,都必须通过<result>节点进行配置。

2.关于抽象方法的声明原则

  • 如果即将执行的是INSERT、UPDATE、DELETE类型的操作,则使用Integer/int作为返回值类型,表示受影响的行数,当然,如果不关心这个返回值,也可以将返回值类型声明为void,但是,并不推荐;如果即将执行的是SELECT类型的操作,返回值类型使用期望的数据类型,同时,需要保证该类型可以封装所需要的查询结果,如果查询结果有多条记录,则应该使用List集合作为抽象方法的返回值类型;
  • 方法名称可以自定义,不允许重载;
  • 参数列表可按需设计。

假设当前需要实现“统计当前数据表中有多少条用户数据”,则可以声明抽象方法为:

Integer count();

接下来,应该在XML文件中配置以上抽象方法对应的SQL语句。

3.配置SQL时需要注意的

  • 应该按需选择所配置的节点,例如执行INSERT操作时就应该使用<insert>节点,尽管执行INSERT操作时也可以使用<update><delete>,但是,绝对不要这样做,如果要执行的是SELECT操作,必须使用<select>节点;
  • 无论是<insert><update><delete><select>中的任何一个,必须配置id属性,取值就是对应的抽象方法的名称;
  • 如果配置的是<select>节点,必须配置resultTyperesultMap属性中的某1个(必须二选一),使用resultType时,表示指定返回值类型,取值为类型的全名,如果抽象方法的返回值类型是List集合类型的,则resultType的值应该是List集合中的元素的类型,也就是说,如果返回值类型是List<User>类型的,则resultType的值应该是User的类型;

所以,关于“统计当前数据表中有多少条用户数据”配置SQL语句的代码为:

<select id="count" resultType="java.lang.Integer">
    SELECT COUNT(*) FROM t_user
</select>

另外,例如需要实现“查询id=?的用户的数据”,可以设计抽象方法为:

User findById(Integer id);

然后,配置的映射(与抽象方法对应的在XML中配置的节点)为:

<select id="findById" resultType="cn.tedu.mybatis.User">
  SELECT * FROM t_user WHERE id=#{id}
</select>

4.关于抽象方法中使用多个参数的问题

假设需要实现“将id=?的用户的邮箱改为?”,则需要执行的SQL语句大致是:

UPDATE t_user SET email=? WHERE id=?

由于以上SQL语句中有2个参数,所以,在设计抽象方法时,抽象方法的参数列表中应该也有2个参数,例如:

Integer updateEmailById(Integer id, String email);

在配置映射时,可以是:

<update id="updateEmailById">
  UPDATE t_user SET email=#{email} WHERE id=#{id}
</update>

但是,以上代码执行时会出现错误:

org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.binding.BindingException: Parameter 'email' not found. Available parameters are [arg1, arg0, param1, param2]

Caused by: org.apache.ibatis.binding.BindingException: Parameter 'email' not found. Available parameters are [arg1, arg0, param1, param2]

也就是说,在调用抽象方法时,给出了2个参数的值,分别是id的值和email的值,这2个值在MyBatis处理时,参数名称其实分别是arg0arg1,或者,使用param1param2也可以访问到这2个参数。

关于以上问题,其根本原因在于Java语言源代码.java文件在执行之前,需要被编译为.class字节码文件,在这个编译过程中,如果没有特殊的干预的情况下,所有局部变量的变量名都是不会被保存的,所以,即使在设计抽象方法时,使用了idemail这样的参数名,其实,在最终运行的.class文件中,根本就没有这样的名称。

当抽象方法的参数只有1个时,MyBatis会自动的使用唯一的那一个参数,所以,在配置SQL映射时,使用的#{}占位符中的名称根本就不重要。在开发时,推荐使用规范的名称,但是,从代码是否可以运行的角度来说,这个占位符中写什么都不重要。

在MyBatis的处理过程中,可以根据argparam作为前缀,按照顺序来确定参数的值,所以,使用第1个参数时,可以使用arg0param1作为名称,第2个可以使用arg1param2 作为名称,如果有更多参数,顺序编号即可,序号可参考抽象方法的声明。

@Param注解

当然,在配置SQL映射时,使用arg0arg1param1param2这样的名称,并不利于代码的阅读和理解,Mybatis框架提供了@Param注解,可以在抽象方法的参数列表中,在每个参数之前添加该注解,并在注解中配置参数的名称:

Integer updateEmailById(
      @Param("id") Integer id, 
      @Param("email") String email
);

后续,在配置SQL映射时,使用的#{}格式的占位符中使用的名字就必须是以上@Param注解中配置的名称

MyBatis实现以上做法的本质其实就是基于注解中配置的参数和调用方法时给出的参数值封装了1个Map!

当然,以上实现原理可以不必过于关心,只需要记住:在使用MyBatis框架,设计抽象方法时,如果参数的数量超过1个(有2个或更多个),就为每一个参数都添加@Param注解,并且,在后续配置SQL映射时,使用的#{}中就使用@Param注解中配置的名称!

5.mapper接口

比如,在申请单管理层持久层接口ApplicationFormMapper中,继承了BaseMapper,泛型为实体:

public interface ApplicationFormMapper extends BaseMapper<ApplicationForm>

    /**
     * 根据申请单id更新 material_flag = 0 字段。
     * 此申请单是否还有物料的标记:0即无物料、1即有物料
     *
     * @param formId 申请单id
     * @return 更新结果
     */
    boolean updateFormByFlag(@Param("formId") String formId);

6.xml文件

Mapper.xml文件就是用于配置SQL语句的文件。

比如:ApplicationFormMapper.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属性:

<!-- namespace属性:当前XML文件对应哪个接口,取值为接口的全名 -->
<mapper namespace="com.qs.modules.applicationForm.mapper.ApplicationFormMapper">

</mapper>

然后在<mapper>的子级添加节点,以对应抽象方法,可以使用<insert><delete><update><select>

应该根据将要执行的SQL语句的类型来选择子级节点。

本次要执行的是UPDATE类型的操作,所以,选择update节点进行配置,该节点的id属性值就是对应的抽象方法的名称:

<!-- id属性:抽象方法的名称,不包含括号及参数列表 -->
    <update id="updateFormByFlag">
        UPDATE
            `application_form`
        set
            material_flag = '0'
         WHERE
            id =  #{formId}
    </update>

在以上节点的内部,就可以开始配置SQL语句,各值使用#{}格式的占位符,占位符中的名称就是抽象方法的参数中@Param("formId")中的名称。

常见的一个错误:Invalid bound statement (not found)。出现该错误,主要是因为MyBatis无法将抽象方法与配置的SQL语句对应所导致的,在查看错误时,应该检查Invalid bound statement (not found)右侧的信息,以确定是哪个抽象方法的对应关系出了问题,然后,再排查:

  • 配置的mapperLocations属性指定了XML文件的位置,但是,XML文件并不在指定的位置;
  • 在配置SQL语句的XML文件中,根节点的namespace属性值错误;
  • 在配置SQL语句的XML文件中,各配置SQL语句的节点,例如节点的id属性值不是抽象方法的名字;

7.动态SQL--foreach

动态SQL:根据调用方法时给出的参数不同,最终生成的SQL语句会不相同!

假设需要实现“删除id=?, id=?, id=?的数据”,即“一次性删除若干条数据”,具体需要删除几条、哪几条,对于开发人员来说,都是不确定的。

实现这样的操作,需要执行的SQL语句大致是:

DELETE FROM t_user WHERE id IN (?,?,?)

以上SQL语句中,问号的数量与值都是开发人员无法确定的,但是,可以肯定的是“这里的问号表示的都是id值”。

实现这个功能,在设计抽象方法时,可以将若干个问号的值,也就是若干个id的值,使用集合或数组来表示,例如:

Integer deleteByIds(List<Integer> ids);

或:

Integer deleteByIds(Integer[] ids);

甚至,还可以将参数声明为可变参数,例如:

Integer deleteByIds(Integer... ids);

附:关于可变参数

// 关于可变参数:
// 1. 每个方法中最多只能有1个可变参数,且必须是方法的最后一个参数;
// 2. 调用这种方法时,可变参数的值的数量可以是0个或若干个;
// 3. 在方法内部处理参数值时,可变参数的值就是一个数组。
public static void sum(int... numbers) {
    int result = 0;
    for (int i = 0; i < numbers.length; i++) {
      result += numbers[i];
    }
    System.out.println(result);
}

完成了抽象方法的声明之后,就需要配置以上抽象方法的映射!配置映射时,需要使用<foreach>节点实现动态SQL中的遍历,即遍历以上方法中的参数值,以生成?,?,?这样的SQL语句片段!

例如,可以配置为:

<!-- Integer deleteByIds(Integer[] ids); -->
<delete id="deleteByIds">
  DELETE FROM t_user WHERE id IN (
    <foreach collection="array" item="id" separator=",">
      #{id}
    </foreach>
  )
</delete>

在以上配置中,各属性的作用:

  • collection:表示被遍历的对象,当抽象方法的参数只有1个且没有添加@Param注解时,如果参数的类型是List集合类型的,则取值为list,如果参数的类型是数组类型的,则取值为array;否则,取值为@Param注解中配置的名称;
  • item:在遍历过程中,被遍历到的数据的名称,将根据这个名称来使用被遍历到的数据,所以,在<foreach>的子级,将根据这个属性配置的值来使用数据;
  • separator:分隔符,用于分隔遍历时生成的多个占位符(也可以理解为遍历产生的多个值);
  • openclose:遍历生成的SQL语句片段的起始字符串和结束字符串。

8. 动态SQL--if

关于<if>的配置语法格式是:

<if test="表达式">
  SQL语句片段
</if>

使用<if>可以根据判断条件不同而得到不同的SQL语句。

在使用<if>时,如果有多个条件存在逻辑关系,可以使用and表示“与”,使用or表示“或”。

在MyBatis中的<if>并没有对应的else标签,所以,只能判断“符合条件时应该怎么执行”,并没有不符合条件时的做法!

如果一定需要实现if...else的效果,需要使用<choose>系列的标签,基本格式是:

<choose>
  <when test="表达式">
    满足条件时的SQL语句片段
  </when>
  <otherwise>
    不满足条件时的SQL语句片段
  </otherwise>
</choose>

9. 关于#{}和${}格式的占位符

在MyBatis中,配置SQL语句时,可以使用#{}${}格式的占位符,用于表示参数的值。

使用#{}格式的占位符时,只能表示某个值,不可以表示SQL语句中的某个片段!使用这种占位符时,MyBatis在处理过程中,是使用预编译的做法,即:使用占位符来填充整个SQL语句,此时,并不关心占位符对应的值是多少,就直接将这样的SQL语句交给数据库进行词法分析、语义分析、编译,编译之后,再将占位符对应的值代进编译好的SQL语句中并执行!

使用${}格式的占位符时,可以表示SQL语句中的任何部分,可以是某个值,也可以是SQL语句中的某个片段!使用这种占位符时,MyBatis在处理过程中,是先将占位符对应的值拼接到SQL语句中,然后,将整个SQL语句交给数据进行词法分析、语义分析、编译,如果无误,就可以直接执行SQL语句,如果出现语法错误,就会导致程序运行过程中出现异常!

使用#{}格式的占位符时,由于使用了预编译的做法,所以,这种处理方式是安全的,而${}占位符是先拼接SQL语句再执行的过程,并没有预编译的处理,所以,存在SQL注入的风险的!

所以,虽然使用${}的占位符可以实现的效果看似更多,但是,需要考虑数据类型的问题(例如字符串类型的值需要自行添加一对单引号),同时还存在SQL注入的风险,一般不推荐使用,应该优先使用#{}格式的占位符!

10.解决名称不匹配导致查询不到数据的问题

结论:MyBatis要求查询结果的列名与封装结果的类中的属性名保持一致,才可以正确的自动封装查询结果

首先,对原有的数据表做调整,增加一个新的字段,表示该用户的部门信息:

ALTER TABLE t_user ADD COLUMN department_id INT;

然后,为现有的用户数据分配部门:

UPDATE t_user SET department_id=1 WHERE id IN (12,17);
UPDATE t_user SET department_id=2 WHERE id IN (3,9);
UPDATE t_user SET department_id=3 WHERE id IN (10,16);

由于数据表中添加了新的字段,在对应的User类中也应该添加新的属性:

private Integer departmentId; // 另补充SET/GET方法,重新生成toString()

接下来,原有的查询功能中,目前并不可以查询到各用户的部门ID值!主要原因是:MyBatis框架在执行查询操作时,会根据查询结果中的列名,将数据封装到结果对象的同名属性中(当然,这个过程其实依然是通过SET/GET方法来实现的)!例如在查询结果中存在username的列,MyBatis就会把这一列的数据封装到User类的username属性中!所以,可以简单的认为:MyBatis要求查询结果的列名与封装结果的类中的属性名保持一致,才可以正确的自动封装查询结果!

当前,在执行查询时,使用的SQL语句是:

SELECT * FROM t_user; // 暂不考虑后面的WHERE子句等部分

由于使用的是星号表示要查询的字段列表,则查询结果的列名与字段的名称是完全一致的,所以,部门ID对应的列名是department_id,而在User类中并没有这个名字对应的属性,在User类存在的是departmentId,这2个名字并不相同,所以,MyBatis无法实现自动封装!

1.别名实现

为了使得MyBatis能够自动封装部门ID的数据,就必须保持名字的统一,但是,肯定不推荐修改User中的属性名或SET方法的名称,那么,就可以调查询结果中的列名,可以通过在SQL语句中定义别名来实现:

SELECT id,username,password,age,phone,email,department_id AS departmentId FROM t_user;

2.resultMap实现

使用<resultMap>解决名称不匹配导致无法查询到某些字段的数据的问题

MyBatis框架在处理查询结果时,要求查询结果中的列名(Column)与封装结果的类的属性名(Property)保持一致!如果在查询的SQL语句中,没有为查询的字段列表取别名,则查询结果中的列名就是字段名(Field)。

字段名(Field):创建数据表时指定的名称,通常,在讨论某个数据表的结构时,就会使用该名称进行描述;

列名(Column):查询结果中,在控制台显示的表格中,第一横排显示的名称;

属性名(Property):Java类中的属性的名称。

当没有自定义别名时,如果名称不匹配,就会导致对应的列的数据无法被封装到查询结果中,此时,可以配置<resultMap>节点,以指导MyBatis框架完成封装。

关于<resultMap> 的配置如下:

<!-- type属性:封装查询结果的类的全名 -->
<!-- id属性:自定义的名称,使用该配置的select节点就必须配置resultMap属性,值就是当前id值 -->
<resultMap type="cn.tedu.mybatis.User" id="UserMap">
    <!-- result节点:将查询结果中哪一列的数据封装到以上type对应的类型的哪个属性中去 -->
    <!-- column属性:查询结果的列名 -->
    <!-- property属性:以上type值对应的类的属性名 -->
    <result column="id" property="id" />
    <result column="username" property="username" />
    <result column="password" property="password" />
    <result column="age" property="age" />
    <result column="phone" property="phone" />
    <result column="email" property="email" />
    <result column="department_id" property="departmentId" />
</resultMap>

<select id="findById" resultMap="UserMap">
    SELECT * FROM t_user WHERE id=#{id}
</select>

注意:如果要使用<resultMap>来决定如何封装查询结果,相关的<select>必须配置resultMap属性,而不能使用resultType属性,并且,在<select>节点中,resultMapresultType属性是二选一的,必须也只能配置其中的1个!

在以上配置中,如果查询结果的列名与封装结果的类的属性名本来就是相同,则可以不必配置对应的<result>节点!即,以上配置可以简化为:

<resultMap type="cn.tedu.mybatis.User" id="UserMap">
    <result column="department_id" property="departmentId" />
</resultMap>

另外,关于主键的配置,推荐使用<id>节点,而不是<result>节点,例如:

<resultMap type="cn.tedu.mybatis.User" id="UserMap">
    <id column="id" property="id" />
    <result column="department_id" property="departmentId" />
</resultMap>

关于主键,无论使用<id>节点,还是使用<result>节点来配置,运行效果是没有区别的,但是,有利MyBatis框架实现缓存机制!

小结:如果出现查询结果的列名与封装结果的类的属性名不一致的问题,可以使用resultType指定查询结果的类的全名,并在查询的SQL语句中,使用自定义别名,保证名称一致,或者,也可以使用resultMap进行配置,在查询的SQL语句中,使用星号(*)表示查询的字段列表即可,这2种解决方案二选一即可!

11. 关联表查询-1

准备工作:先保证当前项目中使用的数据库中有t_department数据表,数据表中有idname字段,并且,有一定的测试数据。

假设需要实现:查询某个用户的信息,并且,要求显示该用户所归属的部门的名称!

实现以上功能,需要执行的SQL语句大致是:

SELECT 
  t_user.*, name 
FROM 
  t_user 
LEFT JOIN 
  t_department 
ON 
  t_user.department_id=t_department.id 
WHERE 
  t_user.id=3;

在实际代码时,还是应该先设计抽象方法,但是,目前没有哪种数据类型能够封装此次的查询结果!所以,需要自行创建新的VO类,用于封装查询结果!

所以,创建名为UserVO的类:

public class UserVO {

  private Integer id;
  private String username;
  private String password;
  private Integer age;
  private String phone;
  private String email;
  private Integer departmentId;
  private String departmentName;
  
  // 补充SET/GET方法和toString()方法
  
}

1.实体类与VO类

与数据表结构保持对应的类,称之为实体类(Entity),例如案例中使用的User类就是实体类;

与查询结果相对应的类,称之为VO类(Value Object),例如以上创建的UserVO类,这种类的设计原则就是:查询结果中有哪些数据,在VO类中就设计哪些属性。

其实,实体类与VO的编码方式、代码结构几乎是一样的,只是定位不同。

然后,就可以设计抽象方法为:

UserVO findVOById(Integer id);

接下来,需要配置以上抽象方法映射的SQL语句,也就是此前分析的SQL语句!

2.不适用*查询字段

一般,在使用关联表查询时,并不推荐使用星号(*)表示要查询的字段列表,同时,有很多数据表中的字段与类中的属性名并不是完全一致的(就好比department_id的问题),所以,一般在编写关联表查询的SQL语句时,就是不使用星号(*)并且将名称不一致的字段取别名!

所以,要配置的SQL映射是:

<select id="findVOById" resultType="cn.tedu.mybatis.UserVO">
  SELECT 
    t_user.id, username, password, age, phone, 
    email, department_id AS departmentId, name AS departmentName
  FROM 
    t_user 
  LEFT JOIN 
    t_department 
  ON 
    t_user.department_id=t_department.id 
  WHERE 
    t_user.id=#{id}
</select>

12.关联表查询-2

假设需要实现:查询某个部门的信息,并且,需要显示该部门有哪些用户。

需要执行的SQL语句大致是:

SELECT
  *
FROM
  t_department
LEFT JOIN
  t_user
ON
  t_department.id=t_user.department_id
WHERE
  t_department.id=1;

在编写代码时,依然需要先设计抽象方法,而设计抽象方法之前,就需要先创建新的VO类,用于封装此次的查询结果!所以,先创建DepartmentVO类:

public class DepartmentVO {
  private Integer id;
  private String name;
  private List<User> users; // 每个部门可以有若干个用户
  
  // 补充SET/GET方法和toString()方法
  
}

1.查询主体不同,接口文件不同

此前使用的UserMapper.java都是配置处理用户数据的相关功能,此次,查询的主体是部门的数据,抽象方法应该另外创建接口来存放,以便于管理数据!(可以放在同一个接口中,但是,不推荐这样做)

所以,应该先创建DepartmentMapper.java接口文件,然后,这个接口中添加抽象方法:

public interface DepartmentMapper {
  
  DepartmentVO findVOById(Integer id);
  
}

2.新的xml文件

完成接口中的抽象方法后,就应该开始配置SQL映射,此前的SomeMapper.xml的根节点对应的是UserMapper接口,所以,将无法对应新创建的DepartmentMapper接口中的抽象方法,并且,出于规范管理代码的要求,也应该将管理部门数据的SQL语句写在新的XML文件中!

所以,先将SomeMapper.xml重命名为UserMapper.xml,再将UserMapper.xml复制得到DepartmentMapper.xml并删除其中的配置!

3.多条数据返回到一个对象中

接下来的任务就是需要配置SQL映射,而此次的查询时,某个部门可能有多个用户,所以结果可能有多条,但是,抽象方法的返回值是1个对象,那么,就存在MyBatis不知道如何将若干个查询结果封装到1个对象中去!所以,在配置时,就需要结合需要执行的SQL语句并使用<resultMap>来完成配置:

<?xml version="1.0" encoding="UTF-8" ?>  
<!DOCTYPE mapper PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"      
 "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd">

<mapper namespace="cn.tedu.mybatis.DepartmentMapper">

  <resultMap id="DepartmentVOMap" type="cn.tedu.mybatis.DepartmentVO">
    <id column="did" property="id" />
    <result column="name" property="name" />
    <!-- collection节点:配置1对多关系的映射,也可以简单的认为是配置List集合类型的属性 -->
    <!-- ofType属性:集合中的元素的数据类型 -->
    <collection property="users" ofType="cn.tedu.mybatis.User">
      <id column="id" property="id" />
      <result column="username" property="username" />
      <result column="password" property="password" />
      <result column="age" property="age" />
      <result column="phone" property="phone" />
      <result column="email" property="email" />
      <result column="department_id" property="departmentId" />
    </collection>
  </resultMap>

  <select id="findVOById" resultMap="DepartmentVOMap">
    SELECT
      t_user.*, 
      t_department.id AS did, name
    FROM
      t_department
    LEFT JOIN
      t_user
    ON
      t_department.id=t_user.department_id
    WHERE
      t_department.id=#{id}
  </select>

</mapper>
posted @ 2022-02-15 11:26  Charles博客  阅读(247)  评论(0编辑  收藏  举报