<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标签即可,而不需要处理其他复杂判断。
在我们大多数需求场景下,mybatis提供的动态语句语法已经可以胜任了,比如if、where、choose、when、otherwise、foreach,再复杂一点的还有set,但是对于复杂需求他们都没办法完美解决(毕竟用if太过繁琐),于是发现了一个灵活性更高的标签——trim。
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拼接优化方案