9.mybatis动态SQL标签的⽤法
动态 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 ⼦句的⼀部分。⽐如:2
4 SELECT * FROM BLOG5 WHERE state = ‘ACTIVE’6 7 AND title like #{title} 8 9
这条语句提供了⼀个可选的⽂本查找类型的功能。如果没有传⼊“title”,那么所有处于“ACTIVE”状态的BLOG都会返回;反之若传⼊
了“title”,那么就会把模糊查找“title”内容的BLOG结果返回(就这个例⼦⽽⾔,细⼼的读者会发现其中的参数值是可以包含⼀些掩码或通配符的)。
如果想可选地通过“title”和“author”两个条件搜索该怎么办呢?⾸先,改变语句的名称让它更具实际意义;然后只要加⼊另⼀个条件即可。2
34 SELECT * FROM BLOG WHERE state = ‘ACTIVE’5 6 AND title like #{title}7
8 9 AND author_name like #{author.name} 10
11
choose, when, otherwise
有些时候,我们不想⽤到所有的条件语句,⽽只想从中择其⼀⼆。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch语句。
还是上⾯的例⼦,但是这次变为提供了“title”就按“title”查找,提供了“author”就按“author”查找,若两者都没有提供,就返回所有符合条件的BLOG(实际情况可能是由管理员按⼀定策略选出BLOG列表,⽽不是返回⼤量⽆意义的随机结果)。23456
SELECT * FROM BLOG WHERE state = ‘ACTIVE’ 7 AND title like #{title}8
9 AND author_name like #{author.name}10 11 12 AND featured = 1
12
13
14
1516
trim, where, set
前⾯⼏个例⼦已经合宜地解决了⼀个臭名昭著的动态 SQL 问题。现在考虑回到“if”⽰例,这次我们将“ACTIVE = 1”也设置成动态的条件,看看会发⽣什么。
2
3 SELECT * FROM BLOG5 WHERE6 7 state = #{state}8
9 10 AND title like #{title} 11
12AND author_name like #{author.name}13
14
15
如果这些条件没有⼀个能匹配上将会怎样?最终这条 SQL 会变成这样:
SELECT * FROM BLOGWHERE
这会导致查询失败。如果仅仅第⼆个条件匹配⼜会怎样?这条 SQL 最终会是这样:
SELECT * FROM BLOGWHERE
AND title like ‘someTitle’
这个查询也会失败。这个问题不能简单的⽤条件句式来解决,如果你也曾经被迫这样写过,那么你很可能从此以后都不想再这样去写了。MyBatis 有⼀个简单的处理,这在90%的情况下都会有⽤。⽽在不能使⽤的地⽅,你可以⾃定义处理⽅式来令其正常⼯作。⼀处简单的修改就能得到想要的效果:
2
35 SELECT * FROM BLOG 6 7 state = #{state}8
9 10 AND title like #{title}11
12 AND author_name like #{author.name}13 14
15 16
where 元素知道只有在⼀个以上的if条件有值的情况下才去插⼊“WHERE”⼦句。⽽且,若最后的内容是“AND”或“OR”开头的,where 元素也知道如何将他们去除。
如果 where 元素没有按正常套路出牌,我们还是可以通过⾃定义 trim 元素来定制我们想要的功能。⽐如,和 where 元素等价的⾃定义 trim元素为:
...
prefixOverrides 属性会忽略通过管道分隔的⽂本序列(注意此例中的空格也是必要的)。它带来的结果就是所有在 prefixOverrides 属性中指定的内容将被移除,并且插⼊ prefix 属性中指定的内容。
类似的⽤于动态更新语句的解决⽅案叫做 set。set 元素可以被⽤于动态包含需要更新的列,⽽舍去其他的。⽐如:
23 update Author 4username=#{username}, 5
password=#{password}, 6
email=#{email}, 7
bio=#{bio} 8
9 where id=#{id}10
11
这⾥,set 元素会动态前置 SET 关键字,同时也会消除⽆关的逗号,因为⽤了条件语句之后很可能就会在⽣成的赋值语句的后⾯留下这些逗号。
若你对等价的⾃定义 trim 元素的样⼦感兴趣,那这就应该是它的真⾯⽬:2注意这⾥我们忽略的是后缀中的值,⽽⼜⼀次附加了前缀中的值。
foreach
动态 SQL 的另外⼀个常⽤的必要操作是需要对⼀个集合进⾏遍历,通常是在构建 IN 条件语句的时候。⽐如:2
3SELECT *
4 FROM POST P5 WHERE ID in
6 8 #{item}9
10
foreach 元素的功能是⾮常强⼤的,它允许你指定⼀个集合,声明可以⽤在元素体内的集合项和索引变量。它也允许你指定开闭匹配的字符串以及在迭代中间放置分隔符。这个元素是很智能的,因此它不会偶然地附加多余的分隔符。
注意 你可以将任何可迭代对象(如列表、集合等)和任何的字典或者数组对象传递给foreach作为集合参数。当使⽤可迭代对象或者数组时,index是当前迭代的次数,item的值是本次迭代获取的元素。当使⽤字典(或者Map.Entry对象的集合)时,index是键,item是值。到此我们已经完成了涉及 XML 配置⽂件和 XML 映射⽂件的讨论。下⼀部分将详细探讨 Java API,这样才能从已创建的映射中获取最⼤利益。
bind
bind 元素可以从 OGNL 表达式中创建⼀个变量并将其绑定到上下⽂。⽐如: SELECT * FROM BLOG WHERE title LIKE #{pattern}
Multi-db vendor support
⼀个配置了“_databaseId”变量的 databaseIdProvider 对于动态代码来说是可⽤的,这样就可以根据不同的数据库⼚商构建特定的语句。⽐如下⾯的例⼦:
2
345 select seq_users.nextval from dual6
7 8 select nextval for seq_users from sysibm.sysdummy1\"9
10
insert into users values (#{id}, #{name})11
12
动态 SQL 中可插拔的脚本语⾔
MyBatis 从 3.2 开始⽀持可插拔的脚本语⾔,因此你可以在插⼊⼀种语⾔的驱动(language driver)之后来写基于这种语⾔的动态 SQL 查询。
可以通过实现下⾯接⼝的⽅式来插⼊⼀种语⾔:
2public interface LanguageDriver {
3 ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);4 SqlSource createSqlSource(Configuration configuration, XNode script, Class> parameterType);5 SqlSource createSqlSource(Configuration configuration, String script, Class> parameterType);6}
⼀旦有了⾃定义的语⾔驱动,你就可以在 mybatis-config.xml ⽂件中将它设置为默认语⾔:23 4
56 7
除了设置默认语⾔,你也可以针对特殊的语句指定特定语⾔,这可以通过如下的 lang 属性来完成:
SELECT * FROM BLOG
或者在你正在使⽤的映射中加上注解 @Lang 来完成:2public interface Mapper {
3 @Lang(MyLanguageDriver.class)4 @Select(\"SELECT * FROM BLOG\")5 List selectBlog();6}注意 可以将 Apache Velocity 作为动态语⾔来使⽤,更多细节请参考 MyBatis-Velocity 项⽬。