Mybatis动态union all通过trim优化&trim标签详解

概述

原始sql

<select id="listUsersInfoIds" resultType="io.github.syske.user.UserInfo">
        select ui.id,
        ui.userId,
        ui.name,
        ui.active
        from (
        <if test="userIds != null and userIds.size > 0">
            select
            u.id,
            u.user_id as userId,
            u.name,
            u.active
            from user u
            where u.id in
            <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
                #{userId, jdbcType=BIGINT}
            </foreach>
            and u.active = true
        </if>
        <if test="postIds != null and postIds.size > 0">
            <if test="userIds != null and and userIds.size > 0">
                union all
            </if>
            select
            u.id,
            u.user_id as userId,
            u.name,
            u.active
            from post_user_mapping m,
            user u
            where m.post_id in
            <foreach collection="postIds" item="postId" open="(" close=")" separator=",">
                #{postId, jdbcType=BIGINT}
            </foreach>
            and m.user_id=u.id
            and u.active = true
        </if>
        <if test="groupIds != null and groupIds.size > 0">
			 <if test="(postIds != null and postIds.size > 0) and (userIds == null or userIds.size == 0)">
			     union all
			</if>
            select
            u.id,
            u.user_id as userId,
            u.name,
            u.active
            from group_user_mapping m,
            user u
            where m.group_id in
            <foreach collection="groupIds" item="groupId" open="(" close=")" separator=",">
                #{groupId, jdbcType=BIGINT}
            </foreach>
            and m.user_id=u.id
            and u.active = true
        </if>
        ) ui group by ui.id
    </select>

我觉得这种方式不够优雅,而且不够灵活,特别是如果我后面还需要加入union all语句的时候,那就要再多判断一个字段,越往后需要判断的字段就越多,然后我再网上找了一圈并没有找到解决方法,最后我打算看下mybatis的文档,幸运的是我还真找到了自己想要的答案。

解决方案

使用trim标签。
优化后的sql。

    <select id="listUsersInfoIds" resultType="net.coolcollege.user.facade.model.user.UserInfo">
        select ui.id,
        ui.userId,
        ui.name,
        ui.active
        from (
        <trim suffixOverrides="union all">
            <trim suffix="union all">
                <if test="userIds != null and userIds.size > 0">
                    select
                    u.id,
                    u.user_id as userId,
                    u.name,
                    u.active
                    from user u
                    where u.id in
                    <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
                        #{userId, jdbcType=BIGINT}
                    </foreach>
                    and u.active = true
                </if>
            </trim>
            <trim suffix="union all">
                <if test="postIds != null and postIds.size > 0">
                    select
                    u.id,
                    u.user_id as userId,
                    u.name,
                    u.active
                    from post_user_mapping m,
                    user u
                    where m.post_id in
                    <foreach collection="postIds" item="postId" open="(" close=")" separator=",">
                        #{postId, jdbcType=BIGINT}
                    </foreach>
                    and m.user_id=u.id
                    and u.active = true
                </if>
            </trim>
            <if test="groupIds != null and groupIds.size > 0">
                select
                u.id,
                u.user_id as userId,
                u.name,
                u.active
                from group_user_mapping m,
                user u
                where m.group_id in
                <foreach collection="groupIds" item="groupId" open="(" close=")" separator=",">
                    #{groupId, jdbcType=BIGINT}
                </foreach>
                and m.user_id=u.id
                and u.active = true
            </if>
        </trim>
        ) ui group by ui.id
    </select>

首先我通过一个大的trim包装所有子查询(之前通过union all连接),条件是移除最后的union all,然后再用一个trim标签包装除最后一个子查询之外的其他子查询,条件是在语句末尾加上union all,这样前面需要通过复杂if判断的语句就直接省略了,而且好处也很明显:
后续不论我增加多少个子查询,我只需要给子查询加上trim标签即可(条件都一样),而不需要关心其他子查询是否为空,这样整个sql不仅更简洁,而且扩展性也很强,后期不论我增加多少个子查询,只需要给子查询加上trim标签即可,而不需要处理其他复杂判断。

trim标签详解

在我们大多数需求场景下,mybatis提供的动态语句语法已经可以胜任了,比如if、where、choose、when、otherwise、foreach,再复杂一点的还有set,但是对于复杂需求他们都没办法完美解决(毕竟用if太过繁琐),于是发现了一个灵活性更高的标签——trim。

简单介绍

trim标签的作用就是帮助我们生成更复杂的sql,关于它的具体作用官方文档并没有给出明确说明,但是根据它的几个参数以及示例,我们可以看出它的用法。我们先看下trim标签的几个属性:

  • suffixOverrides:要替换的后缀(被替换的内容),去除sql语句后面的关键字或者字符,该关键字或者字符由suffixOverrides属性指定,同prefixOverrides。
  • suffix:给sql语句拼接的后缀,表示在trim包裹的SQL末尾添加指定内容(trim包裹的内容为空不添加)。
  • prefixOverrides:要替换的前缀(被替换的内容),去除sql语句前面的关键字或者字符,该关键字或者字符由prefixOverrides属性指定,假设该属性指定为"AND",当sql语句的开头为"AND",trim标签将会去除该"AND"。
  • prefix:给sql语句拼接的前缀,表示在trim包裹的SQL语句前面添加的指定内容(trim包裹的内容为空不添加)。

如果trim标签内的sql语句不存在,不会插入前缀或者后缀,插入前、后缀的前提是trim标签内有sql。

前置用法

先看第一个,也是官方给出的示例——通过trim来实现where标签,用where标签我们通常是这么写的:

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG
  <where>
    <if test="state != null">
         state = #{state}
    </if>
    <if test="title != null">
        AND title like #{title}
    </if>
    <if test="author != null and author.name != null">
        OR author_name like #{author.name}
    </if>
  </where>
</select>

用trim实现的话,可以这样写:

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG
 <trim prefix="where" prefixOverrides="AND | OR">
    <if test="state != null">
         state = #{state}
    </if>
    <if test="title != null">
        AND title like #{title}
    </if>
    <if test="author != null and author.name != null">
        OR author_name like #{author.name}
    </if>
  </trim>
</select>

后置用法

上面我们演示了前置替换的用法,下面我们来看下后置用法,后置用法是通过trim来实现set标签。

<update id="updateAuthorIfNecessary">
  update Author
    <set>
      <if test="username != null">username=#{username},
      <if test="password != null">password=#{password},
      <if test="email != null">email=#{email},
      <if test="bio != null">bio=#{bio}
    </set>
  where id=#{id}
</update>

set标签的作用就是当如上语句中,第四个更新语句为空的时候,会将set标签内末尾的,移除掉,并在标签内语句开始处加上set关键字。用trim标签的话,可以这么写:

<update id="updateAuthorIfNecessary">
  update Author
    <trim prefix="set" suffixOverrides=','>
      <if test="username != null">username=#{username},
      <if test="password != null">password=#{password},
      <if test="email != null">email=#{email},
      <if test="bio != null">bio=#{bio}
    </trim>
  where id=#{id}
</update>

总结

关于trim我们就演示这么多,下面我们做一个简单总结:
prefix:表示前置要插入的内容,比如where、set,它可以单独使用;
suffix:表示后置插入的内容(同prefix);
prefixOverrides:表示前置要移除的内容(中文翻译前置覆写);
suffixOverrides:表示后置要移除的内容(同prefixOverrides);
也就是说trim本质上就是通过这四个属性,实现在语句前后加上或者移除相关内容,来实现复杂的动态sql,在实现方面也很简单,但是灵活度更多。

参考

记一次mybatis复杂动态sql拼接优化方案

你可能感兴趣的:(数据库,mybatis,java,数据库)