Mybatis系列(五)動態SQL
Mybatis系列之動態SQL
引言
凡是寫過資料庫程式的朋友,都能體會到根據不同條件拼接SQL語句的痛苦,在這中間也會犯各種各樣的錯誤,where子句中多個括號,少個空格,set語句中缺個逗號什麼的,各種累覺不愛。使用Mybatis自帶的動態SQL處理機制,可以把我們從這種痛苦中解救出來。今天我們就來講講Mybatis的動態SQL。(本文結尾處有彩蛋噢 :))))
通常使用動態 SQL 不可能是獨立的一部分,MyBatis 當然使用一種強大的動態 SQL 語言來改進這種情形,這種語言可以被用在任意的 SQL 對映語句中。
Mybatis提供了幾下幾種動態SQL元素:
- if
- choose
- foreach
- where / set
- trim
IF元素
在講IF元素之前,我們先回憶一下在不使用Mybatis的情況下,我們是如何拼接WHERE子句的。
通常的做法,是在查詢語句後面跟上一個多餘的WHERE條件,就像下面這樣
select * from table_name where (1=1)
之所以這樣寫,主要是為了後面較方便地把查詢條件直接使用 and XXX 或是 or XXX的形式追加在查詢語句之後,而無需判斷查詢語句中是否有WHERE關鍵字。在拼接的過程中還中注意空格的使用。
有了Mybatis之後,情況就大有不同了,我們再也不是操心SQL語句拼接的問題了。在之前的文章《Mybatis系列之簡單示例》中,曾經出現過IF元素。如:
<select id="queryByName" parameterType="User" resultType="User"> SELECT <include refid="columns"></include> FROM sys_user WHERE is_valid = 1 <if test="userName != null">AND user_name like '%' #{userName} '%'</if> </select>這條語句查詢使用者表,如果沒有傳入userName引數,那麼就查詢出所有is_Valid=1的記錄;反之,如果傳入了userName引數,則查詢is_Valid=1且userName包含傳入值的記錄。
這裡有一點需要提醒各位朋友,IF元素,如果不是配合WHERE元素一起使用,請確保她封裝的只是WHERE條件子句的一部分,而非全部。這句話怎麼理解呢?我們來看個例子,仍用上面的查詢語句,只是略做修改:
<select id="queryByName" parameterType="User" resultType="User"> SELECT <include refid="columns"></include> FROM sys_user WHERE <if test="userName != null">user_name like '%' #{userName} '%'</if> </select>這條對映語句是存在陷阱的。不知道大家有沒有看出來?就是當IF條件不成立的時候,這條SQL語句就會被轉換為
SELECT user_name, user_password, nick_name, email, user_type_id, is_valid, created_time FROM sys_user WHERE
這條查詢語句在資料庫中是無法執行的!!那麼我們要如何修改才能夠避免這種情況發生呢?有兩種方法,一個就是像第一段查詢語句那樣,讓IF元素只是WHERE條件子句的一部分;另一個就是使用Mybaits提供的WHERE元素。這個後面會講到。
CHOOSE元素
有些時候,我們並不是想用到所有條件語句,而是隻從其中選擇一個。針對這種情況Mybatis提供了類似Java中的switch語句的choose元素。
還是用查詢使用者為例,如果查詢條件提供了userName就按userName查詢,如果提供了nickName就按nickName查詢,如果兩者都沒有提供就返回所有有效的使用者(或是符合其他條件的使用者,為簡單起見,這裡就返回所有有效使用者)。
<select id="queryByName" parameterType="User" resultType="User"> SELECT <include refid="columns"></include> FROM sys_user WHERE user_type_id = 1 <choose> <when test="userName != null">user_name like '%' #{userName} '%'</when> <when test="nickName != null">nick_name like '%' #{nickName} '%'</when> <otherwise>is_valid = 1</otherwise> </choose> </select>
既然已經把IF,CHOOSE元素講了,這裡順道也就把FOREACH元素提前講一講,就像是講程式語言的時候,條件語句和迴圈語句總是放在相鄰的章節一樣。
FOREACH元素
這個元素的使用場景是在需要對一個集合進行遍歷的時候使用,如批量刪除、批量插入等語句。
下面就以批量刪除為例,來講一個這個元素的使用。
<!-- 根據傳入的Id值列表,刪除多條記錄 --> <delete id="deleteBatch" parameterType="java.util.List"> DELETE FROM sys_user WHERE user_id in <foreach collection="list" item="item" index="index" open="(" close=")" separator=","> #{item} </foreach> </delete>
我們知道Mybatis進行SQL對映時,傳入引數只能有一個,如果想傳入多個引數,只能使用Java的List或是Array進行封裝後再傳入。上面的語句就是將要刪除的多條記錄的Id值放在了List物件中傳入。
foreach 元素的功能是非常強大的,它允許你指定一個集合,宣告可以用在元素體內的集合項和索引變數。它也允許你指定開閉匹配的字串(上例中的open和close屬性)以及在迭代中間放置分隔符(separator屬性)。這個元素是很智慧的,因此它不會偶然地附加多餘的分隔符。
我們可以將一個 List 例項或者陣列作為引數物件傳給 MyBatis,當我們這麼做的時候,MyBatis 會自動將它包裝在一個 Map 中並以名稱為鍵。List 例項將會以“list”作為鍵,而陣列例項的鍵將是“array”。
WHERE / SET元素
在前面講解IF元素時,我們已經提到了WHERE元素,使用這個元素可以避免在查詢語句中出現只有WHERE關鍵字而沒有作何查詢條件的情況出現。
<select id="queryByName" parameterType="User" resultType="User">
SELECT <include refid="columns"></include>
FROM sys_user
<where>
<if test="userName != null">user_name LIKE '%' #{userName} '%'</if>
<if test="nickName != null"> OR nick_name LIKE '%' #{userName} '%'</if>
</where>
</select>
Mybatis會判斷只有在WHERE元素中至少有一個條件成立時,才會在查詢語句中新增WHERE關鍵字。
細心的朋友可能會有疑問了,在上述SQL語句中,如果第一個條件不成立,而第二個條件成立時,是不是會在WHERE語句中多個OR關鍵字呀?可以告訴大家,完全不心擔心這個問題,Mybatis早已考慮到了,她會將多餘的AND或是OR關鍵字自動剔除掉(所謂多餘,緊跟在WHERE關鍵字後的第一個AND或是OR)。
SET元素和WHERE元素類似,只是她是使用在資料更新語句中而已。
<!-- 更新使用者資訊,並寫回到資料表中 --> <update id="udpateUser" parameterType="User"> UPDATE sys_user <set> <if test="userName != null">user_name = #{userName},</if> <if test="userPassword != null">user_password = #{userPassword},</if> <if test="nickName != null">nick_name = #{nickName},</if> <if test="userTypeId != null">user_type_id = #{userTypeId},</if> <if test="isValid != null">is_valid = #{isValid}</if> </set> WHERE user_id = #{userId} </update>
彩蛋
如果恰好您使用的是Mysql資料庫,那麼這個彩蛋對你而言一定是會讓你驚喜的。
批量插入,這個功能我們大部分朋友在其編寫的程式中會有用到,比如提交一張採購訂單時,我們會對訂單行行遍歷,一筆筆地寫入到資料庫中。這樣操作是可以完成作務的,只是效率上不是很好,因為每次遍歷都需要單獨和資料庫做一次互動,而資料庫的IO操作是很耗效能的。有沒有什麼辦法可以一次性的將所有訂單行寫入到資料庫中呢?這裡介紹一種在Mysql資料庫中的實現方法。
使用Mysql資料庫的朋友都知道,Mysql的insert語句支援一次寫多行資料的,我們就是利用這個語句,實現一次性寫入多條資料。
我們以批量增加使用者為例,將資料封裝在一個List物件中,這樣在對映檔案裡,我們就可以這樣寫:
<!-- 批量新增 --> <insert id="insertBatch" parameterType="java.util.List"> INSERT INTO sys_user(<include refid="columns"></include>) VALUES <foreach collection="list" item="u" separator=","> (#{u.userName}, #{u.userPassword}, #{u.nickName}, #{u.userTypeId}, #{u.isValid}, #{u.createdTime}) </foreach> </insert>
需要注意的是,不要忘記為foreach元素指定separator屬性;另外,在迴圈內部引用物件屬性時,一定要加上字首的。