【MyBatis】(3)xml 映射文件

【MyBatis】(3)xml 映射文件

前言

该篇文章为 xml 映射文件的笔记,主要讲 xml 映射文件(也就是xxxMapper.xml 文件)。

一、mapper(xml文件)

MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。

SQL 映射文件(xml 映射文件)仅有几个顶级元素,按照应被定义的顺序列出。

  • cache:该命名空间的缓存配置。
  • cache-ref:引用其它命名空间的缓存配置。
  • resultMap:描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
  • sql:可被其它语句引用的可重用语句块。
  • insert:映射插入语句。
  • update:映射更新语句。
  • delete:映射删除语句。
  • select:映射查询语句。

上面就是这几个就是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.shengjava.mapper.IUserMapper" >
    <select id="selectUserById" resultType="com.shengjava.pojo.User" parameterType="int">
      select * from User where id = #{id}
    </select>
</mapper>

简单解释一下上面的映射文件文件:

  • 标签:第一行的是XML的声明,其中version属性是必须写的。
  • 标签:是一种标准通用标记语言的文档类型声明,它的目的是要告诉标准通用标记语言解析器它应该使用什么样的文档类型定义(DTD)来解析文档。
  • 标签:其他标签的父标签。namespace属性:映射文件对应的接口全限定路径(推荐这样做,可以查看官网的“探究已映射的 SQL 语句”下的“提示 对命名空间的一点补充”)。

  • select 标签:查询语句标签。

1.select 元素(查询)

select 元素用于写查询查询语句,标签中共有十三个属性。属性设置很多,但是主要的页没几个。详细的属性请参考官方文档

举例:如下就是一个简单的根据用户id查询用户的sql语句。

    <select id="selUser" parameterType="int" resultMap="userResultMap">
      select * from user where user_id = #{id}
    </select>

注意:这边案例的返回值属性resultMap值为userResultMap,这个值为标签中的id,会在下面提到

常用属性:
属性值 | 描述
--- |---
id | 接口中的方法名
parameterType | 传入的参数类型,可以不写。
resultType | 返回结果的类全限定名或别名。(resultType 和 resultMap 之间只能同时使用一个。)
resultMap | 对外部 resultMap 的命名引用。可以在 resultMap 标签元素中建立映射。

2.insert, update 和 delete 元素

DML 语句都非常类似。属性也和select元素属性差不多。

举例:增删改

    <insert id="insUser" parameterType="User">
        insert into user(user_name, user_password, user_age, user_phone, user_email)
        values (#{name }, #{password}, #{age}, #{phone}, #{email});
    </insert>
    
    <delete id="delUser" parameterType="int">
        delete from user where user_id = #{id};
    </delete>

    <update id="updUser" parameterType="User">
        update user
        set user_name=#{name},user_password=#{password},user_age=#{age},user_phone=#{phone},user_email=#{email}
        where user_id=#{id};
    </update>

注意:这里参数parameterType属性值User是实体类,这里我可以直接写User的原因是因为我在xml配置文件(也就是mybatis-config.xml)中配置了别名。也就是在mybatis-config.xml配置文件中的如下这段配置:

    <!-- 给pojo包下类取别名 -->
    <typeAliases>
        <typeAlias alias="User" type="com.shengjava.web.pojo.User"/>
    </typeAliases>

3.sql 元素

这个元素可以用来定义可重用的 SQL 代码片段,以便在其它语句中使用。

<sql id="userColumns"> 这里写sql语句 </sql>

可以使用include标签,在refid属性引用sql代码片段的id。

<include refid="userColumns"></include>,

4.resultMap 元素

resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同等功能的数千行代码。ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。

4.1 常用

resultMap 元素是 MyBatis中最重要最强大的元素。可以建立Java对象属性property与数据库字段column间的映射关系。这也是解决列名不匹配的另外一种方式。(用于java对象属性和数据库字段名字不匹配时)

举例:
一个java实体类

public class User {
    private int id;
    private String name;
    private String password;
    private int age;
    private String phone;
    private String email;
    // 省略get/set
}

结果集元素resultMap,用于建立映射关系。

备注:标签用于主键的映射(property值为java对象属性名称,column值为数据库字段名称),同理标签用于其他java对象属性与数据字段的映射。

    <resultMap id="userResultMap" type="User">
        <id property="id" column="user_id" />
        <result property="name" column="user_name"/>
        <result property="password" column="user_password"/>
        <result property="age" column="user_age"/>
        <result property="phone" column="user_phone"/>
        <result property="email" column="user_email"/>
    </resultMap>

上面定义好了结果集,现在可以在查询返回的结果集中使用。例如:resultMap="resultMap标签中的id"。

    <select id="selUser" parameterType="int" resultMap="userResultMap">
      select * from user where user_id = #{id}
    </select>

备注:查询结果为null,说明POJO中的类属性名称和数据库字段的不一致,

4.2 高级结果映射

resultMap结果集映射元素还包含多个标签,都非常有用。

例如:

  • constructor - 用于在实例化类时,注入结果到构造方法中
  • association – 一个复杂类型的关联;许多结果将包装成这种类型
  • collection – 一个复杂类型的集合
  • discriminator – 使用结果值来决定使用哪个 resultMap

4.高级结果映射(补充)

resultMap 元素有很多子元素和一个值得深入探讨的结构。 下面是resultMap 元素的概念视图。

下表元素来自官网目录“结果映射” 下的下的 “结果映射(resultMap)”

这些元素标签是标签的子元素,标签上面已经使用了,这这边不在过多赘述(详解点击看官网“id & result”小节)。

  • constructor - 用于在实例化类时,注入结果到构造方法中
    • idArg - ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
    • arg - 将被注入到构造方法的一个普通结果
  • id – 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能
  • result – 注入到字段或 JavaBean 属性的普通结果
  • association – 一个复杂类型的关联;许多结果将包装成这种类型
    • 嵌套结果映射 – 关联可以是 resultMap 元素,或是对其它结果映射的引用
  • collection – 一个复杂类型的集合
    • 嵌套结果映射 – 集合可以是 resultMap 元素,或是对其它结果映射的引用
  • discriminator – 使用结果值来决定使用哪个 resultMap
    • case – 基于某些值的结果映射
      • 嵌套结果映射 – case 也是一个结果映射,因此具有相同的结构和元素;或者引用其它的结果映射

4.1 association(关联)(多对一处理)

  • association – 一个复杂类型的关联;许多结果将包装成这种类型
    • 嵌套结果映射 – 关联可以是 resultMap 元素,或是对其它结果映射的引用

关联(association)元素处理“有一个”类型的关系。 (多对一关系)

MyBatis 有两种不同的方式加载关联:

  • 嵌套 Select 查询:通过执行另外一个 SQL 映射语句来加载期望的复杂类型。
  • 嵌套结果映射:使用嵌套的结果映射来处理连接结果的重复子集。

多对一关系:例如,对于学生,多个学生有一个老师,这就是多对一关系。

备注:该小节内容介绍可以查看官网 “结果映射” 下的 “关联

4.1.1 关联的嵌套 Select 查询(类似子查询)

需求:查询学生所有信息(包括学生的教师)。(关联查询,多对一关系)

实体类:Student和Teacher类。学生类Student中包含一个教师属性,用于存放学生的教师的信息。

@Data
public class Student {
    private int id;
    private String name;
    private int tId;
    private Teacher teacher;
}
@Data
public class Teacher {
    private int id;
    private String name;
}

StudentMapper.xml文件

主要查看resultMap标签中的 association标签属性 。标签上有各个属性的解释。更多详细的解释需要查看官网“结果映射”下的“关联的嵌套 Select 查询”

association标签使用的是“关联的嵌套 Select 查询”,就是在该关联中,使用了另一个查询语句(两条sql命令)。(这是第一种方式,还有另一种方式实现)

这种方式类似 ==子查询== ,先查询出一个(教师)再查询另一个(学生),类似sql语句 :
==select * from student where student.t_id = (select teacher.id from teacher)== (注意,这条语句需要加上limit x,1才能成功查询)

    <!--
        需求:查询学生所有信息(包括学生的教师)
        思路:先查询所有学生信息,再用学生的ti_d查询教师信息
        对应的sql语句:select * from student s, teacher t where s.t_id = t.id
     -->
    <select id="selStudents" resultMap="StudentResultMap">
      select * from student
    </select>
    <select id="selTeacherById" parameterType="int" resultType="Teacher">
      select * from teacher where id = #{id}
    </select>
    <!-- 结果映射 -->
    <resultMap id="StudentResultMap" type="Student">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="tId" column="t_id"/>
        <!-- property:java属性名。javaType:java属性的类型(可以填:Java 类的完全限定名,或类型别名)
             column:数据库中的列名,或者是列的别名。select:用于加载复杂类型属性的映射语句的 ID -->
        <association property="teacher" javaType="Teacher" column="t_id" select="selTeacherById"/>
    </resultMap>

测试代码:

public class StudentTest {
    @Test
    public void sel() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 从 SqlSessionFactory 中获取 SqlSession
        try (SqlSession session = sqlSessionFactory.openSession()) {
            StudentMapper mapper = session.getMapper(StudentMapper.class);
            List<Student> students = mapper.selStudents();
            System.out.println(students);
        }
    }
}

输出:(为了排版好看一点,手动将输出的list换行了)

[Student(id=1, name=小明, tId=1, teacher=Teacher(id=1, name=小明)), 
Student(id=2, name=小红, tId=1, teacher=Teacher(id=2, name=小红)), 
Student(id=3, name=小张, tId=1, teacher=Teacher(id=3, name=小张)), 
Student(id=4, name=小李, tId=2, teacher=Teacher(id=4, name=小李)), 
Student(id=5, name=小王, tId=2, teacher=Teacher(id=5, name=小王))]

备注:该小节内容可以查看 “结果映射” 官网下的 “关联的嵌套 Select 查询

4.1.2 关联的嵌套结果映射(类似连表查询)

另一种方式实现上面的查询(使用“关联的嵌套结果映射”)。

使用这种方式仅需要写一条sql命令,个人觉得比较简单,而且结构也比较清楚。只需要在标签下将属性的属性名与数据库字段名(可以是数据库字段名也可以是别名)匹配好就行。

这种方式类似 ==连表查询==。一条语句将两个表关联起来直接查询了。

    <!-- 方式二:关联的嵌套结果映射 -->
    <select id="selStudents" resultMap="StudentResultMap">
      select s.id sid, s.name sname, s.t_id stid, t.id tid, t.name tname
      from student s, teacher t where s.t_id = t.id
    </select>
    <!-- 结果映射 -->
    <resultMap id="StudentResultMap" type="Student">
        <id property="id" column="sid"/>
        <result property="name" column="sname"/>
        <result property="tId" column="stid"/>
        <!-- 查询语句使用正常的查询语句(取了别名),再用association进行映射 -->
        <association property="teacher" javaType="Teacher">
            <id property="id" column="tid"/>
            <result property="name" column="tname"/>
        </association>
    </resultMap>

注意:如果两个表的列名相同,需要取别名。如果需要关联的表属性太多,标签中就会写很多的。此时可以在标签中加上属性resultMap=""。用于引用外部的结果集。官方文档的案例中使用的就是该方式。

重点注意:

非常重要: id 元素在嵌套结果映射中扮演着非常重要的角色。你应该总是指定一个或多个可以唯一标识结果的属性。 虽然,即使不指定这个属性,MyBatis 仍然可以工作,但是会产生严重的性能问题。 只需要指定可以唯一标识结果的最少属性。显然,你可以选择主键(复合主键也可以)。

备注:该小节内容可以查看 “结果映射” 官网下的 “关联的嵌套结果映射

4.2 collection (集合)(一对多处理)

集合元素和关联元素几乎是一样的。就不在提相同之处了,主要关注不同处。

一对多关系:例如,对于老师而言,一个教师有多个学生。

4.2.1 集合的嵌套 Select 查询(子查询)

实体类:还是使用上面的两个实体类,不过需要注意,需要教师类中添加List类型的属性 students。

@Data
public class Teacher {
    private int id;
    private String name;
    /** 教师的学生们 */
    private List<Student> students;
}

TeahcerMapper.xml映射文件。这里面除了标签名不同外,还增加了一个ofType属性,这个主要用于“它用来将 JavaBean(或字段)属性的类型和集合存储的类型区分开来。”也就是泛型中的约束类型。

这种方式类似 ==子查询== ,先查询出一个(教师)再查询另一个(学生),类似sql语句 : select * from student where student.t_id = x (x为teacher表的id值)

    <!-- 第一种方式:集合的嵌套 Select 查询(子查询) -->
    <select id="selTeacher" parameterType="int" resultMap="TeacherResultMap">
      select * from teacher where id = #{id}
    </select>
    <select id="selStudentByTid" parameterType="int" resultType="Student">
      select * from student where t_id = #{id}
    </select>

    <resultMap id="TeacherResultMap" type="Teacher">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <!-- property:java属性。javaType:属性类型(可以不加)。ofType:java属性类型的泛型类型。column:数据库中的列名,或者是列的别名。select:用于加载复杂类型属性的映射语句的 ID -->
        <collection property="students" javaType="list" ofType="Student" column="t_id"  select="selStudentByTid"/>
    </resultMap>

测试:

    @Test
    public void sel() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 从 SqlSessionFactory 中获取 SqlSession
        try (SqlSession session = sqlSessionFactory.openSession()) {
            TeacherMapper mapper = session.getMapper(TeacherMapper.class);
            Teacher teacher = mapper.selTeacher(1);
            System.out.println(teacher);
        }
    }

输出:(备注,学生中的teacher=null是因为我没有去查)

Teacher(id=1, name=秦老师, students=[Student(id=1, name=小明, tId=0, teacher=null), Student(id=2, name=小红, tId=0, teacher=null), Student(id=3, name=小张, tId=0, teacher=null)])

备注:该小节内容可以查看 “结果映射” 官网下的 “集合的嵌套 Select 查询”

4.2.2 集合的嵌套结果映射(连表查询)

这个方式类似上面标签的“集合的嵌套结果映射”,需要关注的是ofType属性。

    <!--第二种方式:集合的嵌套结果映射(连表查询)-->
    <select id="selTeacher" parameterType="int" resultMap="TeacherResultMap">
      select t.id tid, t.name tname, s.id sid, s.name sname, s.t_id stid
      from teacher t, student s where s.t_id=t.id and t.id = #{id}
    </select>
    <resultMap id="TeacherResultMap" type="Teacher">
        <id property="id" column="tid"/>
        <result property="name" column="tname"/>
        <collection property="students" ofType="Student">
            <id property="id" column="sid"/>
            <result property="name" column="sname"/>
            <result property="tId" column="stid"/>
        </collection>
    </resultMap>

备注:该小节内容可以查看 “结果映射” 官网下的 “集合的嵌套结果映射”

参考

官方文档:XMl 映射文件