天天看点

Mybatis 实践篇(三)Mybatis映射文件详解XML映射文件insert | delete | updatesqlselect参数传递结果映射

在我们日常编码中,mapper 文件无疑使我们最重要的工作,因为他承载着我们所有业务中的增删改查所涉及到的 sql 语句编写,那么我们今天就来系统的学习下常用的sql 语句在mybatis 中应该如何编写。

XML映射文件

在开始编写映射文件之前我我们先来认识下几个常用的标签。

  • cache ——对给定命名空间的缓存配置
  • cache-ref ——对其他命名空间缓存配置的引用
  • resultMap ——结果集映射(功能十分强大)
  • sql —— 声明sql片段,可以被其他语句引用
  • insert —— 插入语句
  • update ——更新语句
  • delete ——删除语句
  • select ——查询语句

insert | delete | update

由于在 mybatis 中增删改的操作十分相似,这里就统一说明。在开始之前,先介绍下insert | delete | update 标签中的属性

属性 描述
id 命名空间中的唯一标识符,我们用 mapper 接口的方法名与该id值对应
parameterType 参数类型,为类的全限定名或别名,此属性为可选配置
flushCache 设置为 true 时,在执行sql时或刷新本地缓存和耳机缓存,默认值为 true
timeout 超时时间,默认为未设置
statementType STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
useGeneratedKeys 通过jdbc获取数据库自增主键,默认值为 false
keyProperty mybatis 会将获取到的自增主键的值设置的该属性的值

insert

  • 简单插入
<insert id="saveUser" parameterType="org.bmth.mybatis.entity.User">
    INSERT INTO `users`  (`username`, `password`, `nickname`, `avatar`,`age`, `sex`, `telephone`, `enable`)
    VALUES (#{username}, #{password}, #{nickname}, #{avatar}, #{age}, #{sex},
     #{telephone}, #{enable});
</insert>
           
  • 插入并返回自增主键
<insert id="saveUserAndTakeId" useGeneratedKeys="true" keyProperty="id" parameterType="org.bmth.mybatis.entity.User">
    INSERT INTO `users` (`username`, `password`, `nickname`, `avatar`, `age`, `sex`, `telephone`, `enable`)
    VALUES (#{username}, #{password}, #{nickname}, #{avatar}, #{age}, #{sex},
    #{telephone}, #{enable});
</insert>
           

注: 这里的自增主键的值将会赋值给

User

实体的 id 属性值。在代码中可以通过

User

的实例获取。

  • 批量插入(这里通过

    <foreach>

    标签实现)
<insert id="batchSave">
    INSERT INTO `users` (`username`, `password`, `nickname`, `avatar`, `age`, `sex`, `telephone`, `enable`) VALUES
    <foreach item="item" collection="users" separator=",">
      (#{item.username}, #{item.password}, #{item.nickname}, #{item.avatar}, #{item.age}, #{item.sex}, #{item.telephone}, #{item.enable});
    </foreach>
  </insert>
           

注: 这里的

collection="users"

的 users 属性 来自于 mapper 接口层 ,通过

@Param

标签声名。

delete

<delete id="deleteById" parameterType="java.lang.Integer">
    DELETE FROM `users` WHERE `id`= #{id};
</delete>
           

update

<update id="updateById" parameterType="org.bmth.mybatis.entity.User">
    UPDATE `users` SET `username`=#{username}, `password`=#{password}, `nickname`=#{nickname},
    `avatar`=#{avatar}, `age`=#{age}, `telephone`=#{telephone}, `enable`=#{enable} WHERE `id`=#{id};
  </update>
           

sql

这个元素可以被用来定义可重用的 SQL 代码段,这些 SQL 代码可以被包含在其他语句中。

<sql id="userColumns">
    `username`, `password`, `nickname`, `avatar`, `age`, `sex`, `telephone`,`address`, `enable`
</sql>
           

这个 SQL 片段可以通过

<include>

标签被其他 SQL 语句引用

<select id="findAll" resultType="org.bmth.mybatis.entity.User">
    SELECT
    	<include refid="userColumns"/>
    FROM `users`;
</select>
           

当然,在

<sql>

标签中我们也可以提前声明占位符:

<sql id="userColumns">
    ${alias}.`username`, ${alias}.`password`, ${alias}.`nickname`,
	${alias}.`avatar`,${alias}.`age`,${alias}.`sex`,
    ${alias}.`telephone`,${alias}.`address`,${alias}.`enable`
</sql>
           

然后通过

<include>

标签中进行占位符替换:

<select id="findAll" resultType="org.bmth.mybatis.entity.User">
    SELECT
    <include refid="userColumns">
      <property name="alias" value="u"/>
    </include>
    FROM `users` u;
</select>
           

select

查询绝对在大多数业务场景中都占据这主导地位,而mybatis最强大之处就在于sql语句的灵活性,以及强大的结果集映射。由于结果映射比较复杂,我们会在后面单独来讲。这里先介绍下

<select>

标签中的属性。

属性 描述
id 唯一标识,我们用 mapper 接口的方法名与该id值对应
parameterType 参数类型,为类的全限定名或别名,此属性为可选配置
resultType 返回类型,为类的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身
resultMap 结果集映射,对

<resultMap>

标签命名的引用
flushCache 设置为 true 时,在执行sql时或刷新本地缓存和耳机缓存,默认值为 false,这里需要注意的是,在 insert | update | delete 标签中默认值为 true,在 select 标签中,默认值为 false
useCache 将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。
timeout 超时时间,默认为未设置
fetchSize 尝试让每次批量返回的结果行数和这个设置值相等。 默认值为未设置
statementType STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
resultOrdered 针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。 这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:

false

resultSets 这个设置仅对多结果集的情况适用。它将列出语句执行后返回的结果集并给每个结果集一个名称,名称是逗号分隔的。

看见这么多可设置的属性,是不是感觉我太难了,不用担心,简单的查询其实很简单,比如,我们要根据id查询一个用户:

<select id="findOneById" parameterType="int" resultType="org.bmth.mybatis.entity.User">
    SELECT
    <include refid="userColumns" />
    FROM `users` WHERE `id` = #{id};
</select>
           

这个语句接受一个 int 类型的参数,返回一个 user 类型的结果,这里可能有同学会问,为什么不是

parameterType="java.lang.Integer"

, 上面提到过,这里接收的是一个类的全限定名或者别名,很显然,这里就是 mybatis 为我们预先提供好的一些别名,这里在后面源码分析篇中会很清晰的看到 mybatis为我们提供了很多的别名,来方便我们开发的中使用。

参数传递

我们已经见到过了参数传递的代码,其实并不复杂,而且在我们平时的工作中,也很少用到太过于复杂的参数传递行为。比如在上面的示例中,就是我们常用的一种最简单的参数传递方式:

<select id="findOneById" parameterType="int" resultType="org.bmth.mybatis.entity.User">
    SELECT
    <include refid="userColumns" />
    FROM `users` WHERE `id` = #{id};
</select>
           

这里就是讲一个 int 类型的 id 作为参数传递,当然这里我们还可以传递一个对象类型,比如我们的用户插入操作,就是将一个对象类型作为参数传递,而我们只需要在

#{}

中添加我们想要获取的对象属性即可。

<insert id="saveUser" parameterType="org.bmth.mybatis.entity.User">
    INSERT INTO `users`  (`username`, `password`, `nickname`, `avatar`,`age`, `sex`, `telephone`, `enable`)
    VALUES (#{username}, #{password}, #{nickname}, #{avatar}, #{age}, #{sex},
     #{telephone}, #{enable});
</insert>
           

当然,有些时候单一的参数类型无法满足我们的日常使用,比如我们想要查询 username = ‘小明’ 并且 age = 18 的数据,显然这里我们无法将两个不同类型的参数利用 parameterType 声明,因此,我们需要这样利用

@Param

注解来实现这个功能。

//mapper 接口 
List<User> findAllByUsernameAndAge(@Param("username") String username,
                                   @Param("age") int age);
           
<!--映射文件-->
<select id="findAllByUsernameAndAge" resultType="org.bmth.mybatis.entity.User">
    SELECT
    <include refid="userColumns" />
    FROM `users` WHERE `username` = #{username,jdbcType=VARCHAR} AND `age` = #{age};
</select>
           

可以看到

@Param

注解的值,和

#{}

是绑定的,在 mybatis 中,执行 SQL 语句时,会将 #{} 替换成 ?,这样做可以有效的防止SQL注入,不过有时你就是想直接在 SQL 语句中插入一个不转义的字符串。 比如,像 ORDER BY,你可以使用 ${} 来实现,还是上面的例子,我们做一下修改,可以实现了。

//mapper 接口 
List<User> findAllByUsernameAndAge(@Param("username") String username,
                                   @Param("age") int age,
                                   @Param("columnName") String columnName);
           
<!--映射文件-->
<select id="findAllByUsernameAndAge" resultType="org.bmth.mybatis.entity.User">
    SELECT
    <include refid="userColumns" />
    FROM `users` WHERE `username` = #{username,jdbcType=VARCHAR} AND `age` = #{age} 
    ORDER BY ${columnName} ;
</select>
           

总结:在mybatis中,

${}

会被直接替换,而

#{}

会被使用

?

预处理。

结果映射

可以说,ResultMap 是 MyBatis 中最强大的元素,而且对于简单的映射关系,根本不需要显示的声明,比如我们前面已经见到了简单的映射示例:

简单映射

<!--映射文件-->
<select id="findAllByUsernameAndAge" resultType="org.bmth.mybatis.entity.User">
    SELECT
    <include refid="userColumns" />
    FROM `users` WHERE `username` = #{username,jdbcType=VARCHAR} AND `age` = #{age} 
    ORDER BY ${columnName} ;
</select>
           

这里就是直接将我们所查询的列,映射到了

User

类,那么如果所查询的列和我们所要映射的属性不一致怎么办?其实很简单,我们只需要用 SQL 的 AS 作为别名来匹配即可,比如:

<select id="findAllByUsernameAndAge" resultType="org.bmth.mybatis.entity.User">
    SELECT
    `id`       AS      id,
    `username` AS      username,
    `password` AS      password
    FROM `users` WHERE `username` = #{username,jdbcType=VARCHAR} AND `age` = #{age} 
    ORDER BY ${columnName} ;
</select>
           

当然,这里我们也可以显示的声明

ResultMap

来解决列名不匹配的问题:

<!--resultMap-->
<resultMap id="userResultMap" type="org.bmth.mybatis.entity.User">
    <id property="id" column="id"/>
    <result property="username" column="username" />
    <result property="password" column="password" />
    <result property="age" column="age" />
</resultMap>

<select id="findAllByUsernameAndAge" resultMap="userResultMap">
    SELECT
    `id`, 
    `username`,
    `password`,
    `age`
    FROM `users` WHERE `username` = #{username,jdbcType=VARCHAR} AND `age` = #{age} 
    ORDER BY ${columnName} ;
</select>
           

复杂映射

当然,有很多时候我们需要构造出一个比较复杂的对象,此时

resultMap

的强大之处就得到了很好的体现,假如现在有三张表:用户表 ,收获地址表,以及地址标签表

现在我们需要根据用户来查询到他所有的收货地址,及地址的标签,那么需要构造一个对象如下:

@Data
public class Address {
    private Integer id;
    private String province;
    private String city;
    private String region;
    private String detailAddress;
    private String labelName;
    private String phoneNumber;
}
           
@Data
public class User{
    private Integer id;

    private String username;

    private String password;

    private String avatar;

    private String nickname;

    private Integer sex;

    private Integer age;

    private String telephone;

    private Boolean enable;

    private Date createTime;

    private List<Address> addressList;
}
           

现在我们就利用 resultMap 来映射

User

实体:

<resultMap id="userDetailResultMap" type="org.bmth.mybatis.entity.User">
        <id property="id" column="id"/>
        <result property="username" column="username" />
        <collection property="addressList" ofType="org.bmth.mybatis.entity.Address">
            <id column="address_id" property="id" />
            <result column="province" property="province"/>
            <result column="city" property="city"/>
            <result column="region" property="region"/>
            <result column="detail_address" property="detailAddress"/>
            <result column="region" property="region"/>
            <result column="name" property="labelName"/>
            <result column="phone_number" property="phoneNumber"/>
        </collection>
    </resultMap>

<select id="getUserDetail" parameterType="int" resultMap="userDetailResultMap">
        SELECT
            u.id,
            u.username,
            ra.address_id,
            ra.phone_number,
            ra.province,
            ra.city,
            ra.region,
            ra.detail_address,
            al.name
        FROM
            users u
                INNER JOIN
            receive_address ra ON u.id = ra.user_id
                LEFT JOIN
            address_label al ON ra.label_id = al.label_id
        WHERE u.id = #{userId};
</select>
           

这样就完成了一个比较复杂的映射,

resultMap

的强大之处不仅于此,这里就不一一列举,还是一起来看下

resultMap

的映射结构。

嵌套查询

还是上面的需求,我们还有另一种方式来实现,就是在

resultMap

中做嵌套查询:

<resultMap id="userDetailResultMap2" type="org.bmth.mybatis.entity.User">
        <id property="id" column="id"/>
        <result property="username" column="username" />
        <collection property="addressList" column="id" 		       ofType="org.bmth.mybatis.entity.Address"
                    select="listAddressByUserId"/>
    </resultMap>
    
    <select id="getUserDetail2" parameterType="int" resultMap="userDetailResultMap2">
        SELECT
            u.id,
            u.username
        FROM
            users u
        WHERE u.id = #{userId};
    </select>

    <select id="listAddressByUserId" resultType="org.bmth.mybatis.entity.Address">
        SELECT
            ra.address_id,
            ra.phone_number,
            ra.province,
            ra.city,
            ra.region,
            ra.detail_address,
            al.name
        FROM
            receive_address ra
                LEFT JOIN
            address_label al ON ra.label_id = al.label_id
        WHERE ra.user_id = #{userId};
    </select>
           

resultMap映射

  • constructor

    - 用于在实例化类时,注入结果到构造方法中
    • idArg

      - ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
    • arg

      - 将被注入到构造方法的一个普通结果
  • id

    – 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能
  • result

    – 注入到字段或 JavaBean 属性的普通结果
  • association

    – 一个复杂类型的关联;许多结果将包装成这种类型
    • 嵌套结果映射 – 关联本身可以是一个

      resultMap

      元素,或者从别处引用一个
  • collection

    – 一个复杂类型的集合
    • 嵌套结果映射 – 集合本身可以是一个

      resultMap

      元素,或者从别处引用一个
  • discriminator

    – 使用结果值来决定使用哪个

    resultMap

    • case

      – 基于某些值的结果映射
      • 嵌套结果映射 –

        case

        本身可以是一个

        resultMap

        元素,因此可以具有相同的结构和元素,或者从别处引用一个

        resultMap

        属性列表
属性 描述

id

当前命名空间中的一个唯一标识,用于标识一个结果映射。

type

类的完全限定名, 或者一个类型别名(关于内置的类型别名,可以参考上面的表格)。

autoMapping

如果设置这个属性,MyBatis将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。默认值:未设置。

id & result

<id property="id" column="id"/>
 <result property="username" column="username" />
           

id 元素表示的结果将是对象的标识属性,这会在比较对象实例时用到。 这样可以提高整体的性能,尤其是进行缓存和嵌套结果映射(也就是连接映射)的时候。

两个元素都有一些属性:

属性 描述

property

映射到列结果的字段或属性。如果用来匹配的 JavaBean 存在给定名字的属性,那么它将会被使用

column

数据库中的列名,或者是列的别名。

javaType

一个 Java 类的完全限定名,或一个类型别名

jdbcType

JDBC 类型

typeHandler

我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的完全限定名,或者是类型别名。

association

属性 描述

property

映射到列结果的字段或属性

javaType

一个 Java 类的完全限定名,或一个类型别名

jdbcType

JDBC 类型

typeHandler

类型处理器

collection

<collection property="addressList" ofType="org.bmth.mybatis.entity.Address">
    <id column="address_id" property="id" />
    <result column="province" property="province"/>
    <result column="city" property="city"/>
    <result column="region" property="region"/>
    <result column="detail_address" property="detailAddress"/>
    <result column="region" property="region"/>
    <result column="name" property="labelName"/>
    <result column="phone_number" property="phoneNumber"/>
</collection>
           

association

差不多,不在赘述。

discriminator

一个数据库查询可能会返回多个不同的结果集(但总体上还是有一定的联系的)。 鉴别器(discriminator)元素就是被设计来应对这种情况的,另外也能处理其它情况,例如类的继承层次结构。 鉴别器的概念很好理解——它很像 Java 语言中的 switch 语句。