Mybatis使用:Dynamic Sql

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拼接了。这个对于开发者来说,能够更好的专注于业务逻辑的开发,提升工作效率。 

本文就到这里。

相关推荐