开发者

MyBatis if choose 动态 SQL的实现

目录
  • 动态 SQL
    • if
    • choose、when、otherwise
    • trim、where、set
    • foreach
    • script
    • bind
    • 多数据库支持
    • 动态 SQL 中的插入脚本语言
  • SQL片段拼接#
    • SQL参数取值和OGNL表达式

      动态 SQL

      动态 SQL 是 MyBATis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

      使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。

      如果你之前用过 jsTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。

      • 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>

      这条语句提供了可选的查找文本功能。如果不传入 “title”,那么所有处于 “ACTIVE” 状态的 BLOG 都会返回;如果传入了 “title” 参数,那么就会对 “title” 一列进行模糊查找并返回对应的 BLOG 结果(细心的读者可能会发现,“title” 的参数值需要包含查找掩码或通配符字符)。

      如果希望通过 “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>
      <if test="seat_no != null and seat_no != '' ">  
      	AND seat_no = #{seat_no}  
      </if>   
       <sql id="search">
               <if test="userParams.adminType==null or userParams.adminType==''">
                   and tu.ADMIN_TYPE_ID inwww.devze.com(0,1)
               </if>
      
               <if test="userParams.adminType != null and userParams.adminType != ''">
                   and tu.ADMIN_TYPE_ID=#{userParams.adminType}
               </if>
      
      
               <if test="userParams.roleId != null and userParams.roleId != ''">
                   and (select group_concat(ur.ROLE_ID)
                   from t_user u
                   right join t_user_role ur on ur.USER_ID = u.USER_ID,
                   t_role r
                   where r.ROLE_ID = ur.ROLE_ID
                   and u.USER_ID = tu.USER_ID and r.ROLE_ID=#{userParams.roleId})
               </if>
      
      
               <if test="userParams.mobile != null and userParams.mobile != ''">
                   AND tu.MOBILE =#{userParams.mobile}
               </if>
               <if test="userParams.username != null and userParams.username != ''">
                   AND tu.USERNAME   like CONCAT('%',#{userParams.username},'%')
               </if>
               <if test="userParams.ssex != null and userParams.ssex != ''">
                   AND tu.SSEX  =#{userParams.ssex}
               </if>
               <if test="userParams.status != null and userParams.status != ''">
                   AND tu.STATUS =#{userParams.status}
               </if>
               <if test="userParams.deptId != null and userParams.deptId != ''">
                   AND td.DEPT_ID =#{userParams.deptId}
               </if>
               <if test="userParams.createTime != null and userParams.createTime != ''">
                   AND DATE_FORMAT(tu.CREATE_TIME,'%Y%m%d') BETWEEN substring_index(#{userParams.createTime},'#',1) and substring_index(#{userParams.createTime},'#',-1)
               </if>
           </sql>
      

      choose、when、otherwise

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

      还是上面的例子,但是策略变为:传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就返回标记为 featured 的 BLOG(这可能是管理员认为,与其返回大量的无意义随机 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>
      		<choose>
      		    <when test="……">
      		    	……
      		    </when>
      		    <otherwise>
      		    	……
      		    </otherwise>
      	    </choose>
      
      <select id="findUsersByUser" resultType="cn.soboys.kmall.sys.entity.User">
              select tu.USER_ID,tu.USERNAME,tu.SSEX,td.DEPT_NAME,tu.MOBILE,tu.EMAIL,tu.STATUS,tu.CREATE_TIME,
              td.DEPT_ID
              from t_user tu left join t_dept td on tu.DEPT_ID = td.DEPT_ID
      
              <where>
                  <choose>
                      <when test="userParams.adminType==4">
                          and tu.ADMIN_TYPE_ID in(2,3)
                      </when>
                      <otherwise>
                             <include refid="search"></include> 
                      </otherwise>
                  </choose>
                  
              </where>
      
          </select>
      

      trim、where、set

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

      <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

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

      SELECT * FROM BLOG
      WHERE
      AND title like ‘someTitle'

      这个查询也会失败。这个问题不能简单地用条件元素来解决。这个问题是如此的难以解决,以至于解决过的人不会再想碰到这种问题。

      MyBatis 有一个简单且适合大多数场景的解决办法。而在其他场景中,可以对其进行自定义以符合需求。而这,只需要一处简单的改动:

      <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 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

      如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:

      <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>

      这个例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。

      或者,你可以通过使用trim元素来达到同样的效果:

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

      注意,我们覆盖了后缀值设置,并且自定义了前缀值。

      foreach

      动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。比如:

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

      foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!

      提示 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

      至此,我们已经完成了与 XML 配置及映射文件相关的讨论。下一章将详细探讨 Java API,以便你能充分利用已经创建的映射配置。

      script

      要在带注解的映射器接口类中使用动态 SQL,可以使用 script 元素。比如:

        @Update({"<script>",
            "update Author",
            "  <set>",
            "    <if test='username != null'&http://www.devze.comgt;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}",
            "</script>"})
          void updateAuthorValues(Author author);

      bind

      bind 元素允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文。比如:

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

      多数据库支持

      如果配置了 databaseIdProvider,你就可以在动态代码中使用名为 “_databaseId” 的变量来为不同的数据库构建特定的语句。比如下面的例子:

      <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 版本开始支持插入脚本语言,这允许你插入一种语言驱动,并基于这种语言来编写动态 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);
      }

      实现自定义语言驱动后,你就可以在 mybatis-config.xml 文件中将它设置为默认语言:

      <typeAliases>
        <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>

      或者,在你的 mapper 接口上添加 @Lang 注解:

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

      SQL片段拼接#

      我们再写sql语句的时候往往会有这样一些要求,一些重复的sql语句片段,我们不想重复去写,那么可以通过sql片段方式去抽离,公共sql然后在需要的地方去引用

      MyBatis 中 <sql>元素用于定义一个 SQL 片段,用于分离一些公共的 SQL 语句,例如:SELECT 关键字和 WHERE 关键字之间的部分。其中:

      • id:唯一标识符,用于在其他地方使用 <include> 标签引用;
      • lang:设置字符编码;
      • databaseId:指定执行该 SQL 语句的数据库ID,数据库ID在 mybatis-cfg.xml 中的 中配置。

      同时,你也能够看见 <sql> 标签中可以使用<include>、<trim>、<where>、<set>、<foreach>、<choose>、<if>、<bind>等标签定义复杂的 SQL 片段

      简单使用定编程义sql片段如下:

      <sql id="user_columns">
          `user_id`, `name`, `sex`, `age`
      </sql>
      

      在 <sql> 标签中使用<include> 标签引入定义的sql片段,如下:

      <!-- 定义基础列 -->
      <sql id="user_base_columns">
          `user_id`, `name`
      </sql>
       
      <!-- 定义一个SQL片段 -->
      <sql id="user_columns">
          <include refid="user_base_columns"/>, `sex`, `age`
      </sql>
      

      场景使用案例如:查询用户信息

      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
      <mapper namespace="com.hxstrive.mybatis.sql.demo1.UserMapper">
         <!-- 映射结果 -->
         <resultMap id="RESULT_MAP" type="com.hxstrive.mybatis.sql.demo1.UserBean">
            <id column="user_id" jdbcType="INTEGER" property="userId" />
            <result column="name" jdbcType="VARCHAR" property="name" />
            <result column="sex" jdbcType="VARCHAR" property="sex" />
            <result column="age" jdbcType="INTEGER" property="age" />
         </resultMap>
       
         <!-- 定义一个SQL片段 -->
         <sql id="user_columns">
            `user_id`, `name`, `sex`, `age`
         </sql>
       
         <!-- 查询所有用户信息 -->
         <select id="findAll" resultMap="RESULT_MAP">
            select <include refid="user_columns" /> from `user`
         </select>
       
      </mapper>
      

      SQL参数取值和OGNL表达式

      看到我们上面去值参数通过#{params}这种方式来去值的其中传进来的参数 #{xx} 就是使用的 OGNL 表达式。

      Mybatis 官方文档中「XML 映射文件」模块里边,有解析到:

      说当我们使用 #{} 类型参数符号的时候,其实就是告诉 Mybatis 创建一个预处理语句参数,通过 JDBC,这样的一个参数在 SQL 中会由一个 "?" 来标识,并传递到一个新的预处理语句中。

      MyBatis if choose 动态 SQL的实现

      MyBatis if choose 动态 SQL的实现

      也就是说当我们使用 #{XX} OGNL 表达式的时候, 它会先帮我们生成一条带占位符的 SQL 语句,然后在底层帮我们设置这个参数:ps.setInt(1, id);

      OGNL 是 Object-Graph Navigation Language 的缩写,对象-图行导航语言,语法为:#{ }。

      是不是有点懵,不知道这是个啥?

      OGNL 作用是在对象和视图之间做数据的交互,可以存取对象的属性和调用对象的方法,通过表达式可以迭代出整个对象的结构图

      MyBatis常用OGNL表达式如下:

      MyBatis if choose 动态 SQL的实现

      上述内容只是合适在MyBatis中使用的OGNL表达式,完整的表达式点击这里。

      MyBatis中可以使用OGNL的地方有两处:

      • 动态SQL表达式中
      • ${param}参数中

      如下例子mysql like 查询:

      <select id="xxx" ...>
          select id,name,... from country
          <where>
              <if test="name != null and name != ''">
                  name like concat('%', #{name}, '%')
              </if>
          </where>
      </select>
      

      上面代码中test的值会使用OGNL计算结果。

      例二,通用 like 查询:

      <select id="xxx" ...>
          select id,name,... from country
          <bind name="nameLike" value="'%' + name + '%'"/>
          <where>
              <if test="name != null and name != ''">
                  name like #{nameLike}
              </if>
          </where>
      </select>
      

      这里的value值会使用OGNL计算。

      注:对<bind参数的调用可以通过#{}或 ${} 方式获取,#{}可以防止注入。

      在通用Mapper中支持一种UUID的主键,在通用Mapper中的实现就是使用了标签,这个标签调用了一个静态方法,大概方法如下:

      <bind name="username_bind" 
            value='@java.util.UUID@randomUUID().toString().replace("-", "")' />
      

      这种方式虽然能自动调用静态方法,但是没法回写对应的属性值,因此使用时需要注意。

      • ${params}中的参数

      上面like的例子中使用下面这种方式最简单

      <select id="xxx" ...>
          select id,name,... from country
          <where>
              <if test="name != null and name != ''">
                  name like '${'%' + name + '%'}'
              </if>
          </where>
      </select>
      

      这里注意写的是${'%' + name + '%'},而不是%${name}%,这两种方式的结果一样,但是处理过程不一样。

      MyBatis中处理${}的时候,只是使用OGNL计算这个结果值,然后替换SQL中对应的${xxx},OGNL处理的只是${这里的表达式}。

      这里表达式可以是OGNL支持的所有表达式,可以写的很复杂,可以调用静态方法返回值,也可以调用静态的属性值。

      例子,条件判断入参属性值是否包含子字符串可以直接使用 contains判断

      <foreach collection="list" item="item" index="index" separator="AND" open="(" close=")">
      
          <choose>
              <when test='item.cname.contains("select") or item.cname.contains("checkbox") or item.cname.contains("date")'>
                  <if test='item.cname.contains("select") or item.cname.contains("checkbox")'>
                      find_in_set(#{item.valwww.devze.comue},base.${item.cname})
                  </if>
      
                  <if test='item.cjsname.contains("date")'>
                      DATE_FORMAT(base.${item.cname},'%Y-%m-%d') = DATE_FORMAT(#{item.value},'%Y-%m-%d')
                  </if>
              </when>
              <otherwise>
                  base.${item.cname} = #{item.value}
              </otherwise>
          </choose>
      
      
      </foreach>

      到此这篇关于MyBatis if choose 动态 SQL的实现的文章就介绍到这了,更多相关MyBatis 动态 SQL内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

      0

      上一篇:

      下一篇:

      精彩评论

      暂无评论...
      验证码 换一张
      取 消

      最新开发

      开发排行榜