Mybatis-动态 SQL

Posted on 2016-03-30 10:02  zkongbai  阅读(25168)  评论(4编辑  收藏  举报

MyBatis 的强大特性之一便是它的动态 SQL。

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

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

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

文章最下面包含一些demo.

  • 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”和“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>

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>

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>

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

SELECT * FROM BLOG
WHERE

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

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

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

如果 where 元素没有按正常套路出牌,我们还是可以通过自定义 trim 元素来定制我们想要的功能。比如,和 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 ID in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>

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

注意 You can pass any Iterable object (for example List, Set, etc.), as well as any Map or Array object to foreach as collection parameter. When using an Iterable or Array, index will be the number of current iteration and value item will be the element retrieved in this iteration. When using a Map (or Collection of Map.Entry objects), index will be the key object and item will be the value object.

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

bind

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

<select id="selectBlogsLike" resultType="Blog">
  <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
  SELECT * FROM BLOG
  WHERE 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>

动态 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);
}

一旦有了自定义的语言驱动,你就可以在 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>

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

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

更多参见Mybatis官网 http://mybatis.github.io/mybatis-3/zh/index.html
例子:
 1 <?xml version="1.0" encoding="UTF-8" ?>
 2 <!DOCTYPE mapper PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN" "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd">
 3 
 4 <mapper namespace="cn.ffcs.zone.detailType.persistent.DetailTypeMapper">
 5     <resultMap id="BaseResultMap" type="cn.ffcs.zone.detailType.domain.DetailType">
 6         <result property="id" column="ID" />
 7         <result property="talentId" column="TALENT_ID" />
 8         <result property="typeCode" column="TYPE_CODE" />
 9     </resultMap>
10 
11     <select id="findDetailTypes" resultMap="BaseResultMap">
12         select ID , TYPE_CODE , TALENT_ID
13         from   APP_ZONE_USER_TALENT_TYPE
14     </select>
15     
16     <insert id="saveEntity">
17         <selectKey keyProperty="id" resultType="long" order="BEFORE"> 
18         <![CDATA[
19             select SEQ_TALENT_TYPE_ID.NEXTVAL from dual
20          ]]>
21         </selectKey>
22         
23         INSERT INTO APP_ZONE_USER_TALENT_TYPE(
24             ID,
25             TALENT_ID,
26             TYPE_CODE
27         ) VALUES (
28             #{id,jdbcType=BIGINT},
29             #{talentId,jdbcType=BIGINT},
30             #{typeCode,jdbcType=VARCHAR}
31         )
32     </insert>
33     
34     <insert id="svaeTalentTypeBatch" parameterType="list">
35          INSERT INTO APP_ZONE_USER_TALENT_TYPE
36                   (
37                       ID,TALENT_ID,TYPE_CODE
38                   )
39                   SELECT SEQ_TALENT_TYPE_ID.NEXTVAL,t.* from
40                   (
41                       <foreach collection="list" item="item" index="index" separator="union all">
42                            select 
43                            #{item.talentId,jdbcType=BIGINT},
44                            #{item.typeCode,jdbcType=VARCHAR}
45                         from dual 
46                    </foreach>
47                   ) t
48     </insert>
49     
50     <delete id="delDetailType">
51         delete from APP_ZONE_USER_TALENT_TYPE where ID IN
52         <foreach collection="ids" item="item" index="index" open="(" separator="," close=")">
53             #{item}
54         </foreach>
55     </delete>
56 </mapper>
View Code

 

  1 <?xml version="1.0" encoding="UTF-8" ?>
  2 <!DOCTYPE mapper PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN" "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd">
  3 
  4 <mapper namespace="cn.ffcs.zone.userCard.persistent.UserCardMapper">
  5     <resultMap id="BaseResultMap" type="cn.ffcs.zone.userCard.domain.UserCard">
  6         <result property="id" column="ID" />
  7         <result property="userId" column="USER_ID" />    
  8         <result property="cardNo" column="CARD_NO" />
  9         <result property="cardType" column="CARD_TYPE" />    
 10         <result property="cyUserId" column="CY_USER_ID" />                
 11         <result property="createdt" column="CREATEDT" />
 12         <result property="updatedt" column="UPDATEDT" />
 13         <result property="cardTypeDefault" column="CARD_TYPE_DEFAULT" />
 14     </resultMap>
 15     
 16     <sql id="columns">
 17         <![CDATA[
 18             ID,
 19             USER_ID,
 20             CARD_NO,
 21             CARD_TYPE,
 22             CY_USER_ID,
 23             CREATEDT,
 24             UPDATEDT,
 25             CARD_TYPE_DEFAULT
 26         ]]>
 27     </sql>
 28 
 29     <insert id="saveUserCard">
 30         <selectKey keyProperty="id" resultType="long" order="BEFORE"> 
 31         <![CDATA[
 32             select SEQ_AZ_USER_CARD_ID.NEXTVAL from dual
 33          ]]>
 34         </selectKey>
 35         INSERT INTO APP_ZONE_USER_CARD (
 36         ID,
 37         USER_ID,
 38         CARD_NO,    
 39         CARD_TYPE,    
 40         CY_USER_ID,
 41         CREATEDT,
 42         UPDATEDT,
 43         CARD_TYPE_DEFAULT
 44         ) VALUES (
 45         #{id,jdbcType=BIGINT},
 46         #{userId,jdbcType=BIGINT},
 47         #{cardNo,jdbcType=VARCHAR},
 48         #{cardType,jdbcType=VARCHAR},
 49         #{cyUserId,jdbcType=VARCHAR},
 50         sysdate,
 51         sysdate,
 52         #{cardTypeDefault,jdbcType=VARCHAR}
 53         )
 54     </insert>
 55 
 56 
 57     <update id="updateUserCardById">
 58         update APP_ZONE_USER_CARD set
 59         <if test="@Ognl@isNotNull(cardType)"> 
 60             CARD_TYPE= #{cardType,jdbcType=VARCHAR},
 61         </if>
 62         <if test="@Ognl@isNotNull(cardNo)"> CARD_NO= #{cardNo,jdbcType=VARCHAR},</if>
 63         <if test="@Ognl@isNotNull(cardTypeDefault)"> CARD_TYPE_DEFAULT= #{cardTypeDefault,jdbcType=BIGINT},</if>
 64         UPDATEDT=sysdate
 65         where ID= #{id,jdbcType=BIGINT}
 66     </update>
 67 
 68 
 69     <update id="updateByUserId">
 70         update APP_ZONE_USER_CARD set
 71         <if test="@Ognl@isNotNull(cardType)"> 
 72             CARD_TYPE= #{cardType,jdbcType=VARCHAR},
 73         </if>
 74         <if test="@Ognl@isNotNull(cardNo)"> CARD_NO= #{cardNo,jdbcType=VARCHAR},</if>
 75         UPDATEDT=sysdate
 76         where USER_ID= #{userId,jdbcType=BIGINT}
 77             <if test="cardType == 2">
 78                 and CARD_TYPE = 2
 79             </if>
 80     </update>
 81 
 82 
 83    <update id="updateByCyUserId">
 84         update APP_ZONE_USER_CARD set
 85         <if test="@Ognl@isNotNull(cardType)"> 
 86             CARD_TYPE= #{cardType,jdbcType=VARCHAR},
 87         </if>
 88         <if test="@Ognl@isNotNull(cardNo)"> CARD_NO= #{cardNo,jdbcType=VARCHAR},</if>
 89         UPDATEDT=sysdate
 90         where CY_USER_ID= #{cyUserId,jdbcType=VARCHAR}
 91     </update>
 92 
 93 
 94     <select id="findByParam" resultMap="BaseResultMap">
 95         select * from app_zone_user_card t where 1=1
 96         <if test=" cardType!=null and cardType!=''">
 97             and t.CARD_TYPE=#{cardType,jdbcType=VARCHAR}
 98         </if>
 99         <if test="cardNo!=null and cardNo!=''">
100             and t.CARD_NO= #{cardNo,jdbcType=VARCHAR}
101         </if>
102         <if test="userId!=null and userId!=''">
103             and t.USER_ID= #{userId,jdbcType=BIGINT}
104         </if>
105         <if test="cyUserId!=null and cyUserId!=''">
106             and t.CY_USER_ID= #{cyUserId,jdbcType=VARCHAR}
107         </if>
108         <if test="cardTypeDefault!=null and cardTypeDefault!=''">
109             and t.CARD_TYPE_DEFAULT= #{cardTypeDefault,jdbcType=BIGINT}
110         </if>
111     </select>
112 
113 </mapper>
View Code

 java代码

package cn.ffcs.zone.userCard.persistent;

import java.util.List;
import java.util.Map;

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.ResultMap;
import org.apache.ibatis.annotations.Select;

import cn.ffcs.mybatis.BaseMapper;
import cn.ffcs.zone.userCard.domain.UserCard;


public interface UserCardMapper extends BaseMapper<UserCard>{

		
	public int saveUserCard(UserCard uc);
	
	
	@Select(" select * from app_zone_user_card t where t.user_id=#{userId} ")
    @ResultMap("BaseResultMap")
    public UserCard findByUserId(@Param("userId") Long userId);
	
	
	@Select(" select * from app_zone_user_card t where t.id=#{id} ")
    @ResultMap("BaseResultMap")
    public UserCard findById(@Param("id") Long id);
	
	
	//根据 用户id 获取卡号信息
	@Select(" select * from app_zone_user_card t where t.CY_USER_ID=#{cjUserId} ")
    @ResultMap("BaseResultMap")
    public UserCard findByCyId(@Param("cjUserId") String  cjUserId);
	
	//根据主键id  更新
	 public int updateUserCardById(UserCard uc);
	 
	 //根据用户id 更新
	 public int updateByUserId(UserCard uc);
	 
	 //根据创寓 用户id 更新
	 public int updateByCyUserId(UserCard uc);
	 
	 public List<UserCard> findByParam(Map<String, Object> param);
	 
}

  

 1 package cn.ffcs.mybatis;
 2 
 3 import java.io.Serializable;
 4 import java.util.List;
 5 import java.util.Map;
 6 
 7 import org.apache.ibatis.session.RowBounds;
 8 
 9 public interface BaseMapper<T> {
10 
11     public int saveEntity(T entity);
12 
13     public int updateEntity(T entity);
14 
15     public T findByPK(Serializable entityId);
16 
17     public long findCountByCriteria(Map<String, Object> param);
18 
19     public List<T> findPageListByCriteria(Map<String, Object> para, RowBounds bounds);
20 
21     public int deleteByPKArray(Serializable[] ids);
22 }
BaseMapper