MyBatis 的強大特性之一便是它的動態 SQL。
如果你有使用 JDBC 或其他類似框架的經驗,你就能體會到根據不同條件拼接 SQL 語句有多麼痛苦。拼接的時候要確保不能忘了必要的空格,還要注意省掉列名列表最後的逗號。利用動態 SQL 這一特性可以徹底擺脫這種痛苦。
通常使用動態 SQL 不可能是獨立的一部分,MyBatis 當然使用一種強大的動態 SQL 語言來改進這種情形,這種語言可以被用在任意的 SQL 映射語句中。
動態 SQL 元素和使用 JSTL 或其他類似基於 XML 的文本處理器相似。在 MyBatis 之前的版本中,有很多的元素需要來了解。MyBatis 3 大大提升了它們,現在用不到原先一半的元素就可以了。MyBatis 采用功能強大的基於 OGNL 的表達式來消除其他元素。
文章最下面包含一些demo.
動態 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>
有些時候,我們不想用到所有的條件語句,而只想從中擇其一二。針對這種情況,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>
前面幾個例子已經合宜地解決了一個臭名昭著的動態 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>
注意這裡我們忽略的是後綴中的值,而又一次附加了前綴中的值。
動態 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 元素可以從 OGNL 表達式中創建一個變量並將其綁定到上下文。比如:
<select id="selectBlogsLike" resultType="Blog"> <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" /> SELECT * FROM BLOG WHERE title LIKE #{pattern} </select>
一個配置了“_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>
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(); }
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