动态 SQL

MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其他类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句有多么痛苦。拼接的时候要确保不能忘了必要的空格,还要注意省掉列名列表最后的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。

通常使用动态 SQL 不可能是独立的一部分,MyBatis 当然使用一种强大的动态 SQL 语言来改进这种情形,这种语言可以被用在任意的 SQL 映射语句中。

动态 SQL 元素和使用 JSTL 或其他类似基于 XML 的文本处理器相似。在 MyBatis 之前的版本中,有很多的元素需要来了解。MyBatis 3 大大提升了它们,现在用不到原先一半的元素就可以了。MyBatis 采用功能强大的基于 OGNL 的表达式来消除其他元素。

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

if

动态 SQL 通常要做的事情是有条件地包含 where 子句的一部分。比如:

<select id="findActiveBlogWithTitleLike"resultType="Blog">SELECT * FROM BLOG WHERE state = ‘ACTIVE’ <if test="title != null">AND title like #{title}</if>
</select> id="findActiveBlogWithTitleLike"resultType="Blog">SELECT * FROM BLOG WHERE state = ‘ACTIVE’ <if test="title != null">AND title like #{title}</if>
</select>

这条语句提供了一个可选的文本查找类型的功能。如果没有传入“title”,那么所有处于“ACTIVE”状态的BLOG都会返回;反之若传入了“title”,那么就会把模糊查找“title”内容的BLOG结果返回(就这个例子而言,细心的读者会发现其中的参数值是可以包含一些掩码或通配符的)。

如果想可选地通过“title”和“author”两个条件搜索该怎么办呢?首先,改变语句的名称让它更具实际意义;然后只要加入另一个条件即可。

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

choose, when, otherwise

有些时候,我们不想用到所有的条件语句,而只想从中择其一二。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

还是上面的例子,但是这次变为提供了“title”就按“title”查找,提供了“author”就按“author”查找,若两者都没有提供,就返回所有符合条件的BLOG(实际情况可能是由管理员按一定策略选出BLOG列表,而不是返回大量无意义的随机结果)。

<select id="findActiveBlogLike"resultType="Blog">SELECT * FROM BLOG WHERE state = ‘ACTIVE’<choose><when test="title != null">AND title like #{title}</when><when test="author != null and author.name != null">AND author_name like #{author.name}</when><otherwise>AND featured = 1</otherwise></choose>
</select> id="findActiveBlogLike"resultType="Blog">SELECT * FROM BLOG WHERE state = ‘ACTIVE’<choose><when test="title != null">AND title like #{title}</when><when test="author != null and author.name != null">AND author_name like #{author.name}</when><otherwise>AND featured = 1</otherwise></choose>
</select>

trim, where, set

前面几个例子已经合宜地解决了一个臭名昭著的动态 SQL 问题。现在考虑回到“if”示例,这次我们将“ACTIVE = 1”也设置成动态的条件,看看会发生什么。

<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">AND author_name like #{author.name}</if>
</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">AND author_name like #{author.name}</if>
</select>

如果这些条件没有一个能匹配上将会怎样?最终这条 SQL 会变成这样:

SELECT * FROM BLOG
WHERE* FROM BLOG
WHERE

这会导致查询失败。如果仅仅第二个条件匹配又会怎样?这条 SQL 最终会是这样:

SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’* FROM BLOG
WHERE
AND title like ‘someTitle’

这个查询也会失败。这个问题不能简单的用条件句式来解决,如果你也曾经被迫这样写过,那么你很可能从此以后都不想再这样去写了。

MyBatis 有一个简单的处理,这在90%的情况下都会有用。而在不能使用的地方,你可以自定义处理方式来令其正常工作。一处简单的修改就能得到想要的效果:

<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">AND author_name like #{author.name}</if></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">AND author_name like #{author.name}</if></where>
</select>

where 元素知道只有在一个以上的if条件有值的情况下才去插入“WHERE”子句。而且,若最后的内容是“AND”或“OR”开头的,where 元素也知道如何将他们去除。

如果 where 元素没有按正常套路出牌,我们还是可以通过自定义 trim 元素来定制我们想要的功能。比如,和 where 元素等价的自定义 trim 元素为:

<trim prefix="WHERE" prefixOverrides="AND |OR ">...
</trim> prefix="WHERE" prefixOverrides="AND |OR ">...
</trim>

prefixOverrides 属性会忽略通过管道分隔的文本序列(注意此例中的空格也是必要的)。它带来的结果就是所有在 prefixOverrides 属性中指定的内容将被移除,并且插入 prefix 属性中指定的内容。

类似的用于动态更新语句的解决方案叫做 set。set 元素可以被用于动态包含需要更新的列,而舍去其他的。比如:

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

这里,set 元素会动态前置 SET 关键字,同时也会消除无关的逗号,因为用了条件语句之后很可能就会在生成的赋值语句的后面留下这些逗号。

若你对等价的自定义 trim 元素的样子感兴趣,那这就应该是它的真面目:

<trim prefix="SET" suffixOverrides=",">...
</trim> prefix="SET" suffixOverrides=",">...
</trim>

注意这里我们忽略的是后缀中的值,而又一次附加了前缀中的值。

foreach

动态 SQL 的另外一个常用的必要操作是需要对一个集合进行遍历,通常是在构建 IN 条件语句的时候。比如:

<select id="selectPostIn" resultType="domain.blog.Post">SELECT *FROM POST PWHERE ID in<foreach item="item" index="index" collection="list"open="(" separator="," close=")">#{item}</foreach>
</select> id="selectPostIn" resultType="domain.blog.Post">SELECT *FROM POST PWHERE ID in<foreach item="item" index="index" collection="list"open="(" separator="," close=")">#{item}</foreach>
</select>

foreach 元素的功能是非常强大的,它允许你指定一个集合,声明可以用在元素体内的集合项和索引变量。它也允许你指定开闭匹配的字符串以及在迭代中间放置分隔符。这个元素是很智能的,因此它不会偶然地附加多余的分隔符。

注意 你可以将任何可迭代对象(如列表、集合等)和任何的字典或者数组对象传递给foreach作为集合参数。当使用可迭代对象或者数组时,index是当前迭代的次数,item的值是本次迭代获取的元素。当使用字典(或者Map.Entry对象的集合)时,index是键,item是值。

到此我们已经完成了涉及 XML 配置文件和 XML 映射文件的讨论。下一部分将详细探讨 Java API,这样才能从已创建的映射中获取最大利益。

bind

bind 元素可以从 OGNL 表达式中创建一个变量并将其绑定到上下文。比如:

<select id="selectBlogsLike" resultType="Blog"><bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />SELECT * FROM BLOGWHERE title LIKE #{pattern}
</select> id="selectBlogsLike" resultType="Blog"><bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />SELECT * FROM BLOGWHERE title LIKE #{pattern}
</select>

Multi-db vendor support

一个配置了“_databaseId”变量的 databaseIdProvider 对于动态代码来说是可用的,这样就可以根据不同的数据库厂商构建特定的语句。比如下面的例子:

<insert id="insert"><selectKey keyProperty="id" resultType="int" order="BEFORE"><if test="_databaseId == 'oracle'">select seq_users.nextval from dual</if><if test="_databaseId == 'db2'">select nextval for seq_users from sysibm.sysdummy1"</if></selectKey>insert into users values (#{id}, #{name})
</insert> id="insert"><selectKey keyProperty="id" resultType="int" order="BEFORE"><if test="_databaseId == 'oracle'">select seq_users.nextval from dual</if><if test="_databaseId == 'db2'">select nextval for seq_users from sysibm.sysdummy1"</if></selectKey>insert into users values (#{id}, #{name})
</insert>

动态 SQL 中可插拔的脚本语言

MyBatis 从 3.2 开始支持可插拔的脚本语言,因此你可以在插入一种语言的驱动(language driver)之后来写基于这种语言的动态 SQL 查询。

可以通过实现下面接口的方式来插入一种语言:

public interface LanguageDriver {ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
} interface LanguageDriver {ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}

一旦有了自定义的语言驱动,你就可以在 mybatis-config.xml 文件中将它设置为默认语言:

<typeAliases><typeAlias type="org.sample.MyLanguageDriver" alias="myLanguage"/>
</typeAliases>
<settings><setting name="defaultScriptingLanguage" value="myLanguage"/>
</settings><typeAlias type="org.sample.MyLanguageDriver" alias="myLanguage"/>
</typeAliases>
<settings><setting name="defaultScriptingLanguage" value="myLanguage"/>
</settings>

除了设置默认语言,你也可以针对特殊的语句指定特定语言,这可以通过如下的 lang 属性来完成:

<select id="selectBlog" lang="myLanguage">SELECT * FROM BLOG
</select> id="selectBlog" lang="myLanguage">SELECT * FROM BLOG
</select>

或者在你正在使用的映射中加上注解 @Lang 来完成:

public interface Mapper {@Lang(MyLanguageDriver.class)@Select("SELECT * FROM BLOG")List<Blog> selectBlog();
} interface Mapper {@Lang(MyLanguageDriver.class)@Select("SELECT * FROM BLOG")List<Blog> selectBlog();
}

注意 可以将 Apache Velocity 作为动态语言来使用,更多细节请参考 MyBatis-Velocity 项目。

你前面看到的所有 xml 标签都是默认 MyBatis 语言提供的,它是由别名为 xml 语言驱动器 org.apache.ibatis.scripting.xmltags.XmlLanguageDriver 驱动的。

mybatis动态sql标签的使用相关推荐

  1. 9.mybatis动态SQL标签的用法

    mybatis动态SQL标签的用法 动态 SQL MyBatis 的强大特性之一便是它的动态 SQL.如果你有使用 JDBC 或其他类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句有多么痛苦 ...

  2. Mybatis动态sql标签说明

    一.简述 1️⃣config 文件常用标签 properties 标签:引入外部 properties 文件资源. settings 标签:设置 mybatis 全局行为. typeAlias 标签: ...

  3. MyBatis——动态SQL语句——if标签和where标签复合使用

    功能需求 根据性别和名字查询用户 官方文档 MyBatis--动态 SQL SQL语句 SELECT id, username, birthday, sex, address FROM `user` ...

  4. Mybatis 动态sql语句(if标签和where标签)

    功能:根据性别和名字查询用户 查询sql语句: SELECT id, username, birthday, sex, address FROM `user` WHERE sex = 1 AND us ...

  5. Mybatis系列全解(八):Mybatis的9大动态SQL标签你知道几个?提前致女神!

    封面:洛小汐 作者:潘潘 2021年,仰望天空,脚踏实地. 这算是春节后首篇 Mybatis 文了~ 跨了个年感觉写了有半个世纪 - 借着女神节 ヾ(◍°∇°◍)ノ゙ 提前祝男神女神们越靓越富越嗨森! ...

  6. 面试题—Mybatis动态sql是什么意思?常用的标签有哪些和用途?

    传统jdbc方法中,在写组合的多表复杂sql语句时,需要去拼接sql语句,稍不注意少写一个空格或"",就会导致报错. 这个Mybatis动态sql的功能,就拥有有效的解决了这个问题 ...

  7. Mybatis中XML的文件SQL语句与动态sql标签(trim|where|set|foreach|if|choose|when|otherwise|bind)

    在xml中写sql语句有很多的规范.标准,有时候想要找对应的例子还得翻一翻以前的代码,这里干脆对遇到的情况做个记录. 一,新建XML文件 <?xml version="1.0" ...

  8. MyBatis动态SQL(认真看看, 以后写SQL就爽多了)

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 来源:cnblogs.com/homejim/p/9909657. ...

  9. 认真看看, 以后写 SQL 就爽多了:MyBatis 动态 SQL:

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 作者 | 阿进的写字台 来源 | cnblogs.com ...

最新文章

  1. 通过minify将项目中js和css文件的打包
  2. 百度计算生物研究登上Nature子刊!将3D结构引入分子表征,结果超越斯坦福MIT,已落地制药领域...
  3. 文章id 文章标题点击量php,WordPress如何通过文章ID获取文章标题等信息
  4. LSGO软件技术团队2015~2016学年第十周(1102~1108)总结
  5. ORM(手写简单ORM)
  6. tar 命令详解 / xz 命令
  7. LeetCode57 Insert Interval
  8. Windows 10 开启 Num Lock
  9. 解决webstorm本地IP访问页面出错的问题,webstorm支持IP访问
  10. 美颜重磅技术之GPUImage源码分析
  11. 动画中英文对照表(09.24)
  12. 基于单片机的音频信号分析仪毕业设计
  13. oracle 回收站清空,Oracle 清空回收站
  14. 【02.06-02.13】长赢计划主理人“ETF拯救世界”微博精选
  15. Instability Analysis and Oscillation Suppression of Enhancement-Mode GaN Devices--GaN半桥电路中不稳定分析
  16. 用 .pth 文件附加 Python 模块搜索路径
  17. 机器人搭建记录 HoshinoBot
  18. 维修服务器bga是什么,服务器主板芯片坏了有机器能拆除焊接BGA吗?
  19. Windows下使用windeployqt打包QML项目(双击运行无反应)
  20. idea美化插件推荐

热门文章

  1. python3数据科学入门与实战_Python3学习之Python3数据科学入门与实战视频教程
  2. 表格在首行,添加空行
  3. 数据结构排序3-堆排序
  4. python列表对应元素合并为列表及判断一个列表是几维
  5. Python中的异常处理Try...except...finally的使用的简单理解
  6. 【学习总结】GirlsInAI ML-diary day-11-while循环
  7. [NOIP2003]传染病控制题解
  8. 《Java技术》第一次作业
  9. SSAS parent/child dimension
  10. php取整函数ceil,floor,round,intval函数的区别