Mybatis 实践篇(三)Mybatis映射文件详解

在我们日常编码中,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超时时间,默认为未设置
statementTypeSTATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
useGeneratedKeys通过jdbc获取数据库自增主键,默认值为 false
keyPropertymybatis 会将获取到的自增主键的值设置的该属性的值

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 标签声名。

 void batchSave(@Param("users") List<User> users);

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尝试让每次批量返回的结果行数和这个设置值相等。 默认值为未设置
statementTypeSTATEMENT,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 类的完全限定名,或一个类型别名
jdbcTypeJDBC 类型
typeHandler我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的完全限定名,或者是类型别名。

association

属性描述
property映射到列结果的字段或属性
javaType一个 Java 类的完全限定名,或一个类型别名
jdbcTypeJDBC 类型
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 语句。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值