cuterabbitbaby 2013-02-21
Mybatis使用:Dynamic Sql
本节我们学习下Mybatis最强大的特性之一。
如果你使用过JDBC来拼接SQL语句,后者有其它简单ORM框架使用经验的话,你会明白根据根据条件拼接SQL是多么痛苦,少个括号,逗号,空格都是一种灾难。你需要仔细的测试你的SQL生成的各种结果来确保SQL拼接覆盖到了各种情况。这种情况特别影响心情,还有可能将隐患带到线上。如果出现这种情况的话,你听到bug的第一印象就是动态拼接SQL的问题,抓狂的感觉可想而知啦。
而使用Mybatis的Dynamic SQL能够让你处理、远离这种痛苦。
好吧,上面这段类似宣传的说明就是说明一件事:使用mybatis吧,Dynamic SQL特性能帮助你解决条件SQL拼接的问题。当然,前提是建立在你能熟悉、熟练Mybatis的Dynamic SQL特性的基础之上。
说了这么多,Dynamic SQL究竟能有多强大呢,我们通过例程来看下。
注意本文的Dynamic SQL我以前用过,现在回头看下特性差别不大,就不在单独写SQL验证,以官方的SQL教程为准,会加上一些自己的理解和使用场景,还有一些教程不太容易理解的地方。官方的Dynamic SQL地址见这里:http://www.mybatis.org/core/dynamic-sql.html
Dynamic SQL支持下面四种条件SQL:
if choose (when, otherwise) trim (where, set) foreach
我们分别来学习下这四种条件SQL的使用。
if
这个SQL是在Dynamic SQL里面使用最多,也是最为重要的一个,使用方法倒不难,看下if条件在where条件语句中的情况:
<select id="findActiveBlogWithTitleLike" parameterType="Blog" resultType="Blog"> SELECT * FROM BLOG WHERE state = ‘ACTIVE’ <if test="title != null"> AND title like #{title} </if> </select>
这个SQL提供基本的查询功能state='active',另外提供一个可选的条件title:如果你传了title这个参数,就会添加上title这个条件。
如果想选择多个条件的话,if语句时可以叠加使用的:
<select id="findActiveBlogWithTitleAndCategoryLike" parameterType="Blog" resultType="Blog"> SELECT * FROM BLOG WHERE state = ‘ACTIVE’ <if test="title != null"> AND title like #{title} </if> <if test="category != null"> AND category like #{category} </if> </select>
如果你添加的调试时Blog实体类中的对象的条件,如下面的author,判断author.name是否为空:
<select id="findActiveBlogLike" parameterType="Blog" 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 where other
在有些情况下,你不需要所有的条件,需要在多个条件中进行选择,如Java中的Switch或者if...else...判断:
<select id="findActiveBlogLike" parameterType="Blog" 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>
根据test的结果添加条件,如果条件都不满足,则设定默认的条件,这个功能还是比较实用的,关键是思路清晰,易于理解。
trim, where, set
如果where后面的条件都是可选项怎么办?这三个参数就是解决这个问题的。这几个测试条件都比较特殊,需要仔细阅读体会。
<select id="findActiveBlogLike" parameterType="Blog" 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
这个明显是个Error1064语法错误,如果仅仅匹配第二个条件的话,SQL回事这样:
SELECT * FROM BLOG WHERE AND title like ‘someTitle’
这个也是个语法错误。对于where后面全部是条件判断的话,就不能用单纯的if语句了。
Mybatis的处理时将where也作为条件,这个是Dynamic SQL新添加的功能:
<select id="findActiveBlogLike" parameterType="Blog" 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元素能根据被包含的判断,做出是否插入的判断;并且能够判断出AND和OR开头的内容。
在官方文档中还有一句话,如果上面的where没有按照期望工作的话,使用trim元素来自定义。这句话比较奇怪,莫非是官方不相信这个功能的准确性。
<select id="findActiveBlogLike" parameterType="Blog" 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"> AND author_name like #{author.name} </if> </trim> </select>
prefixOverrides属性会根据前缀覆盖内容,当然有前缀覆盖,也一定有后缀覆盖。
在使用动态更新时,使用类似的解决方案是set,set元素可以更新需要更新的列,而不需要全部更新;这个能让我们的SQL自行判断是否需要更新,如果你不设定值得花,SQL会忽略掉更新。这个会避免我们会将null值更新到数据库去,在很多情况下这个是不允许的。
<update id="updateAuthorIfNecessary" parameterType="domain.blog.Author"> 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>
更新数据时,只要设定需要更新的部分即可,不用全部重新设定。
相对应的trim元素可以写成这样:
<update id="updateAuthorIfNecessary" parameterType="domain.blog.Author"> update Author <trim prefix="SET",suffixOverrides=","> <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> </trim> where id=#{id} </update>
trim会覆盖掉后缀的逗号,使得SQL能够正常提交。
foreach
最后一个Dynamic SQL的通用操作是迭代,迭代集合还是很常见的,这通常在构建IN条件中出现,如:
<select id="selectPostIn" resultType="domain.blog.Post"> SELECT * FROM POST P WHERE ID in <foreach item="item" index="index" collection="list" open="(" separator="," close=")"> #{item} </foreach> </select>
foreach操作非常强大;它能够根据你指定的集合,将其迭代应用在集合元素内。foreach能够指定开放和关闭的标记,并且能够指定迭代之间的分隔符,这个对于获得数据库指定多个状态的数据时很有效的。
bind
在前面我们看到过like的例子,在哪个例子中,你需要自行添加好匹配符之后,在传给SQL。如果你只想传入Bill,来获得author.name中包含有Bill的作家名称,你需要使用bind操作OGNL表达式:
<select id="selectBlogsLike" parameterType="Blog" resultType="Blog"> <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" /> SELECT * FROM BLOG WHERE title LIKE #{pattern} </select>
你只用传入Bill,就可以查询title中包含有Bill的title;而在前面中,你需要将title设定为'%Bill%',这个操作才是真正的like含义。
最后说下Dynamic SQL的插件式的脚本特性,这个需要最新mybatis 3.2的支持。
mybatis支持两种内置语言,xml和raw;上面讨论的都是xml,能够支持上面提到的所有元素。
但是raw语言的话就不会支持全部;当你使用raw格式来设定SQL时,Mybatis仅仅做参数替换后就将替换参数后的SQL提交给数据库。如你所见,对于Mybatis的Dynamic SQL特性来说,raw就是一种倒退。但是raw依然存在,因为它比xml格式要快。
对于raw的使用,实际上在开发者,如果你的SQL本身就比价简单,就不用经过一系列动态SQL解析,直接提交给数据库即可,下面我们看下例程:
<select id="selectBlog" lang="raw"> SELECT * FROM BLOG </select>
如果使用Annotation的话,可以这么使用:
public interface Mapper { @Lang(RawLanguageDriver.class) @Select("SELECT * FROM BLOG") List<Blog> selectBlog(); }
如果想创建自己的LanguageDriver的话,你需要实现LanguageDriver接口:
public interface LanguageDriver { public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql); public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType); public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType); }
如果你确认自己的SQL仅作参数替换,而不需要Dynamic特性的话,就可以设定自己的SQL为raw,提升性能。
Dynamic SQL的特性就这么多,如果理解深刻、使用熟练的话,那在工作中是如虎添翼,不用考虑SQL拼接了。这个对于开发者来说,能够更好的专注于业务逻辑的开发,提升工作效率。
本文就到这里。