Mybatis动态标签学习笔记

310 阅读10分钟
  • 建议1:对于绝大数查询,我们是返回统一字段,所以可以使用<sql /> 标签,定义SQL段。对于性能或者查询字段比较大的查询,按需要的字段查询。

  • 建议2:对于数据库的关键字,使用大写。例如说,SELECTWHERE 等等。

  • 建议 3 :基本是每“块”数据库关键字占用一行

  • 示例

    <?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="cn.iocoder.springboot.lab12.mybatis.mapper.UserMapper">
    
        <sql id="FIELDS">
            id, username, password, create_time
        </sql>
    
        <insert id="insert" parameterType="UserDO" useGeneratedKeys="true" keyProperty="id">
            INSERT INTO users (
              username, password, create_time
            ) VALUES (
              #{username}, #{password}, #{createTime}
            )
        </insert>
    
        <update id="updateById" parameterType="UserDO">
            UPDATE users
            <set>
                <if test="username != null">
                    , username = #{username}
                </if>
                <if test="password != null">
                    , password = #{password}
                </if>
            </set>
            WHERE id = #{id}
        </update>
    
        <delete id="deleteById" parameterType="Integer">
            DELETE FROM users
            WHERE id = #{id}
        </delete>
    
        <select id="selectById" parameterType="Integer" resultType="UserDO">
            SELECT
                <include refid="FIELDS" />
            FROM users
            WHERE id = #{id}
        </select>
    
        <select id="selectByUsername" parameterType="String" resultType="UserDO">
            SELECT
                <include refid="FIELDS" />
            FROM users
            WHERE username = #{username}
            LIMIT 1
        </select>
    
        <select id="selectByIds" resultType="UserDO">
            SELECT
                <include refid="FIELDS" />
            FROM users
            WHERE id IN
                <foreach item="id" collection="ids" separator="," open="(" close=")" index="">
                    #{id}
                </foreach>
        </select>
    
    </mapper>
    

if标签

  • if 标签 : 内嵌于 select / delete / update / insert 标签,如果满足 test 属性的条件,则执行代码块
  • test 属性 :作为 if 标签的属性,用于条件判断,使用 OGNL 表达式。
<select id="findUser">
    select * from User where 1=1
    <if test=" age != null ">
        and age > #{age}
    </if>
    <if test=" name != null ">
        and name like concat(#{name},'%')
    </if>
</select>

choose标签、when标签、otherwise标签

  • choose 标签:顶层的多分支标签,单独使用无意义
  • when 标签:内嵌于 choose 标签之中,当满足某个 when 条件时,执行对应的代码块,并终止跳出 choose 标签,choose 中必须至少存在一个 when 标签,否则无意义
  • otherwise 标签:内嵌于 choose 标签之中,当不满足所有 when 条件时,则执行 otherwise 代码块,choose 中 至多 存在一个 otherwise 标签,可以不存在该标签
  • test 属性 :作为 when 与 otherwise 标签的属性,作为条件判断,使用 OGNL 表达式
<select id="findUser">
    select * from User where 1=1 
    <choose>
        <when test=" age != null ">
            and age > #{age}
        </when>
        <when test=" name != null ">
            and name like concat(#{name},'%')
        </when>
        <otherwise>
            and sex = '男'
        </otherwise>
    </choose>
</select>

很明显,choose 标签作为多分支条件判断,提供了更多灵活的流程控制,同时 otherwise 的出现也为程序流程控制兜底,有时能够避免部分系统风险、过滤部分条件、避免当程序没有匹配到条件时,把整个数据库资源全部查询或更新。

foreach标签

没错,确实 Mybatis 提供了 foreach 标签来处理这几类需要遍历集合的场景,foreach 标签作为一个循环语句,他能够很好的支持数组、Map、或实现了 Iterable 接口(List、Set)等,尤其是在构建 in 条件语句的时候,我们常规的用法都是 id in (1,2,3,4,5 ... 100) ,理论上我们可以在程序代码中拼接字符串然后通过 ${ ids } 方式来传值获取,但是这种方式不能防止 SQL 注入风险,同时也特别容易拼接错误,所以我们此时就需要使用 #{} + foreach 标签来配合使用,以满足我们实际的业务需求。譬如我们传入一个 List 列表查询 id 在 1 ~ 100 的用户记录:

<select id="findAll">
    select  * from user where ids in 
    <foreach collection="list"
        item="item" index="index" 
        open="(" separator="," close=")">
            #{item}
    </foreach>
</select>

最终拼接完整的语句就变成:

select  * from user where ids in (1,2,3,...,100);

当然你也可以这样编写:

<select id="findAll">
    select  * from user where 
    <foreach collection="list"
        item="item" index="index" 
        open=" " separator=" or " close=" ">
            id = #{item}
    </foreach>
</select>

最终拼接完整的语句就变成:

select  * from user where id =1 or id =2 or id =3  ... or id = 100;
  • foreach 标签:顶层的遍历标签,单独使用无意义
  • collection 属性:必填,Map 或者数组或者列表的属性名(不同类型的值获取下面会讲解)
  • item 属性:变量名,值为遍历的每一个值(可以是对象或基础类型),如果是对象那么依旧是 OGNL 表达式取值即可,例如 #{item.id} 、#{ user.name } 等
  • index 属性:索引的属性名,在遍历列表或数组时为当前索引值,当迭代的对象时 Map 类型时,该值为 Map 的键值(key)
  • open 属性:循环内容开头拼接的字符串,可以是空字符串
  • close 属性:循环内容结尾拼接的字符串,可以是空字符串
  • separator 属性:每次循环的分隔符

第一,当传入的参数为 List 对象时,系统会默认添加一个 key 为 'list' 的值,把列表内容放到这个 key 为 list 的集合当中,在 foreach 标签中可以直接通过 collection="list" 获取到 List 对象,无论你传入时使用 kkk 或者 aaa ,都无所谓,系统都会默认添加一个 key 为 list 的值,并且 item 指定遍历的对象值,index 指定遍历索引值。

// java 代码
List kkk = new ArrayList();
kkk.add(1);
kkk.add(2);
...
kkk.add(100);
sqlSession.selectList("findAll",kkk);
<!-- xml 配置 -->
<select id="findAll">
    select  * from user where ids in 
    <foreach collection="list"
        item="item" index="index" 
        open="(" separator="," close=")">
            #{item}
    </foreach>
</select>

第二,当传入的参数为数组时,系统会默认添加一个 key 为 'array' 的值,把列表内容放到这个 key 为 array 的集合当中,在 foreach 标签中可以直接通过 collection="array" 获取到数组对象,无论你传入时使用 ids 或者 aaa ,都无所谓,系统都会默认添加一个 key 为 array 的值,并且 item 指定遍历的对象值,index 指定遍历索引值。

// java 代码
String [] ids = new String[3];
ids[0] = "1";
ids[1] = "2";
ids[2] = "3";
sqlSession.selectList("findAll",ids);
<!-- xml 配置 -->
<select id="findAll">
    select  * from user where ids in 
    <foreach collection="array"
        item="item" index="index" 
        open="(" separator="," close=")">
            #{item}
    </foreach>
</select>

第三,当传入的参数为 Map 对象时,系统并 不会 默认添加一个 key 值,需要手工传入,例如传入 key 值为 map2 的集合对象,在 foreach 标签中可以直接通过 collection="map2" 获取到 Map 对象,并且 item 代表每次迭代的的 value 值,index 代表每次迭代的 key 值。其中 item 和 index 的值名词可以随意定义,例如 item = "value111",index ="key111"。

<!-- xml 配置 -->
<select id="findAll">
    select  * from user where
    <foreach collection="map2"
        item="value111" index="key111" 
        open=" " separator=" or " close=" ">
        id = #{value111}
    </foreach>
</select>

可能你会觉得 Map 受到不公平对待,为何 map 不能像 List 或者 Array 一样,在框架默认设置一个 'map' 的 key 值呢?但其实不是不公平,而是我们在 Mybatis 框架中,所有传入的任何参数都会供上下文使用,于是参数会被统一放到一个内置参数池子里面,这个内置参数池子的数据结构是一个 map 集合,而这个 map 集合可以通过使用 “_parameter” 来获取,所有 key 都会存储在 _parameter 集合中,因此:

  • 当你传入的参数是一个 list 类型时,那么这个参数池子需要有一个 key 值,以供上下文获取这个 list 类型的对象,所以默认设置了一个 'list' 字符串作为 key 值,获取时通过使用 _parameter.list 来获取,一般使用 list 即可。
  • 同样的,当你传入的参数是一个 array 数组时,那么这个参数池子也会默认设置了一个 'array' 字符串作为 key 值,以供上下文获取这个 array 数组的对象值,获取时通过使用 _parameter.array 来获取,一般使用 array 即可。
  • 但是!当你传入的参数是一个 map 集合类型时,那么这个参数池就没必要为你添加默认 key 值了,因为 map 集合类型本身就会有很多 key 值,例如你想获取 map 参数的某个 key 值,你可以直接使用 _parameter.name 或者 _parameter.age 即可,就没必要还用 _parameter.map.name 或者 _parameter.map.age ,所以这就是 map 参数类型无需再构建一个 'map' 字符串作为 key 的原因,对象类型也是如此,例如你传入一个 User 对象。

因此,如果是 Map 集合,你可以这么使用:

// java 代码
Map map2 = new HashMap<>();
map2.put("k1",1);
map2.put("k2",2);
map2.put("k3",3); 
sqlSession.selectList("findAll",map2);

直接使用 collection="_parameter",你会发现神奇的 key 和 value 都能通过 _parameter 遍历在 index 与 item 之中。

<!-- xml 配置 --><select id="findAll">
    select  * from user where
    <foreach collection="_parameter"
         item="value111" index="key111"
         open=" " separator=" or " close=" ">
        id = #{value111}
    </foreach>
</select>

foreach标签的insert、update、delete用法

  • insert

    批量插入 100 条用户记录:

    <insert id="insertUser" parameterType="java.util.List">
        insert into user(id,username) values
        <foreach collection="list" 
             item="user" index="index"
             separator="," close=";" >
            (#{user.id},#{user.username})
        </foreach>
    </insert>
    
  • update

    更新 500 个用户的姓名:

    <update id="updateUser" parameterType="java.util.List">
        update user 
           set username = '潘潘' 
         where id in 
        <foreach collection="list"
            item="user" index="index" 
            separator="," open="(" close=")" >
            #{user.id}    
        </foreach>
    </update>
    
  • delete

    删除 10 条用户记录:

    <delete id="deleteUser" parameterType="java.util.List">
        delete from user
              where id in
        <foreach collection="list"
             item="user" index="index"
             separator="," open="(" close=")" >
            #{user.id}
        </foreach>
    </delete>
    

where标签、set标签

  • where

    之前我们使用1=1的写法,有了where标签之后,我们只需把 where 关键词以及 1=1 改为 < where > 标签即可,另外还有一个特殊的处理能力,就是 where 标签能够智能的去除(忽略)首个满足条件语句的前缀,例如以上条件如果 age 和 name 都满足,那么 age 前缀 and 会被智能去除掉,无论你是使用 and 运算符或是 or 运算符,Mybatis 框架都会帮你智能处理。

    <select id="findUser">
        select * from User 
        <where>
            <if test=" age != null ">
                and age > #{age}
            </if>
            <if test=" name != null ">
                and name like concat(#{name},'%')
            </if>
        </where>
    </select>
    

    用法特别简单,我们用官术总结一下

    where 标签:顶层的遍历标签,需要配合 if 标签使用,单独使用无意义,并且只会在子元素(如 if 标签)返回任何内容的情况下才插入 WHERE 子句。另外,若子句的开头为 “AND” 或 “OR”,where 标签也会将它替换去除。

    <select id="findUser">
        select * from User 
        <where>
            <if test=" age != null ">
                and age > #{age}
            </if>
            <if test=" name != null ">
                and name like concat(#{name},'%')
            </if>
        </where>
    </select>
    

    如果 age 传入有效值 10 ,满足 age != null 的条件之后,那么就会返回 where 标签并去除首个子句运算符 and,最终的 SQL 语句会变成:

    select * from User where age > 10; 
    -- and 巧妙的不见了
    
    • 值得注意的是,where 标签 只会 智能的去除(忽略)首个满足条件语句的前缀,所以就建议我们在使用 where 标签的时候,每个语句都最好写上 and 前缀或者 or 前缀
    • 另外还有一个值得注意的点,我们使用 XML 方式配置 SQL 时,如果在 where 标签之后添加了注释,那么当有子元素满足条件时,除了 < !-- --> 注释会被 where 忽略解析以外,其它注释例如 // 或 / / 或 -- 等都会被 where 当成首个子句元素处理,导致后续真正的首个 AND 子句元素或 OR 子句元素没能被成功替换掉前缀,从而引起语法错误!**
  • set

    • 用法与where标签与元素相似
    • set 标签:顶层的遍历标签,需要配合 if 标签使用,单独使用无意义,并且只会在子元素(如 if 标签)返回任何内容的情况下才插入 set 子句。另外,若子句的 开头或结尾 都存在逗号 “,” 则 set 标签都会将它替换去除。
    • 另外需要注意,set 标签下需要保证至少有一个条件满足,否则依然会产生语法错误;同时每个条件子句都建议在句末添加逗号,最后一个条件语句可加可不加。或者每个条件子句都在句首添加逗号,第一个条件语句可加可不加;
    • 与 where 标签相同,我们使用 XML 方式配置 SQL 时,如果在 set 标签子句末尾添加了注释,那么当有子元素满足条件时,除了 < !-- --> 注释会被 set 忽略解析以外,其它注释例如 // 或 /**/ 或 -- 等都会被 set 标签当成末尾子句元素处理,导致后续真正的末尾子句元素的逗号没能被成功替换掉后缀,从而引起语法错误!

trim标签

  • prefix :前缀,当 trim 元素内存在内容时,会给内容插入指定前缀
  • suffix :后缀,当 trim 元素内存在内容时,会给内容插入指定后缀
  • prefixesToOverride :前缀去除,支持多个,当 trim 元素内存在内容时,会把内容中匹配的前缀字符串去除。
  • suffixesToOverride :后缀去除,支持多个,当 trim 元素内存在内容时,会把内容中匹配的后缀字符串去除。

bind标签

简单来说,这个标签就是可以创建一个变量,并绑定到上下文,即供上下文使用,就是这样,我把官网的例子直接拷贝过来:

<select id="selecUser">
  <bind name="myName" value="'%' + _parameter.getName() + '%'" />
  SELECT * FROM user
  WHERE name LIKE #{myName}
</select>

sql标签+include标签

sql 标签与 include 标签组合使用,用于 SQL 语句的复用,日常高频或公用使用的语句块可以抽取出来进行复用

简单的复用代码块可以是:

<!-- 可复用的字段语句块 -->
<sql id="userColumns">
    id,username,password 
</sql>

查询或插入时简单复用:

<!-- 查询时简单复用 -->
<select id="selectUsers" resultType="map">
  select
    <include refid="userColumns"></include> 
  from user 
</select>

<!-- 插入时简单复用 -->
<insert id="insertUser" resultType="map">
  insert into user(
    <include refid="userColumns"></include> 
  )values(
    #{id},#{username},#{password} 
  )  
</insert>