以下是我們資料庫表 tb_user 的記錄:

假設現在有一個需求,就是根據輸入的使用者年齡和性别,查詢使用者的記錄資訊。你可能會說,這太簡單了,腦袋裡立馬蹦出如下的 SQL 語句:
你可能會覺得這條 SQL 語句還不夠完美,因為使用者名和年齡是輸入的參數,不是寫死,應該用占位符替換一下,是以修改如下:
你可能認為一切到此結束了,但是你想過沒有還有以下這些情況?
輸入的年齡和性别都是 null
輸入的性别有值,但年齡為 null
輸入的年齡有值,但性别 null
輸入的年齡和性别都有值
現在明白了吧,你其實隻處理了以上四種情況中的一種,具體而言是最後一種,還有三種情況并沒有處理。
你可能覺得,我看不出剩下的三種情況與已經處理的最後那種情況有什麼差別。
好吧,我們一起看看吧,我把 SQL 語句改為第一種情況,也就是年齡和性别都是 null,如下:
執行結果如下:
這個結果并不是我們想要的,因為當輸入的年齡和性别參數為空,正确結果應該是查到所有 user 記錄才對。
是以,正确的 SQL 語句應該如下:
這四種情況的處理,一條 SQL 語句是搞不定的,應該要用四條 SQL 語句。
以上情況就是所謂動态條件查詢,也就是當查詢條件動态改變時,不同的查詢條件對應不同的 SQL 語句。
之前我們動态查詢條件是年齡和性别,那麼如果我再增加一個查詢條件,比如姓名,這又會有多少種情況呀?
相信你很快就有答案了,是八種,沒錯吧。
怎麼得來的呀,這很簡單,就是數學的排列組合。當兩個動态查詢條件,是四種處理情況,也就是2的平方;當三個動态查詢條件,就是2的三次方,有八種;以此類推,當有四個動态查詢條件,那麼就是2的四次方,有十六種。
你可能會想,如果有四種動态查詢條件,我得寫十六條 SQL 語句,這也太誇張了吧。
那有沒有什麼辦法,無論有多少個動态查詢條件,都隻需要寫一條 SQL 語句。
答案是,有,當然有啦,那就是 MyBatis 動态 SQL。
動态 SQL 是 MyBatis 的一個強大的特性之一,它提供了 OGNL 表達式動态生成 SQL 的功能。
if 語句用來解決動态條件查詢問題,它可以實作根據條件拼接 SQL 語句,也就是一條 SQL 語句搞定動态條件查詢哈。
我們還是用之前年齡和性别兩個動态條件查詢,來說一說 if 語句的用法。
Mapper 接口方法:
SQL 語句映射:
SQL 語句映射(增加動态條件查詢):
接下來,我們分别測試動态條件查詢的四種情況,測試前記得要打開 log4j 的 debug 日志開關,這樣才能看到 MyBatis 在調試日志中生成的 SQL 語句。
現在執行之前寫的 JUnit 測試方法,如下:
以上執行結果和以前相同,是四種情況中最後一種,生成的 SQL 語句如下:
我把查詢條件修改一下,如下:
再執行測試,生成的 SQL 語句如下:
相信看到這裡,你應該大緻明白 if 語句的作用了吧。還有兩種情況,我們繼續修改查詢條件,如下:
最後一種情況了,修改查詢條件,如下:
執行測試,生成的 SQL 語句如下:
if 語句的作用好比 Java 中的 if 語句,它根據 test 判斷條件如果為 true 則拼接裡面包含的 SQL 語句片段,如果為 false 則不拼接 SQL 語句片段,文法如下:
這裡要注意一下,test 的判斷條件直接是參數名,而不需要加 #{} 或者 ${}。
以後遇到動态條件查詢,有多少個查詢條件就寫多少個 if 語句即可,是不是很友善呀。
可能有同學還會有一個疑惑,就是為什麼要在 where 後加上 1=1 ,感覺怪怪的。其實,很容易想明白,目的是為了拼接 SQL 語句時文法正确。如果不加的話,可能會出現如下 SQL 語句:
這條 SQL 語句明顯文法錯誤,現在應該明白加上 1=1 的用處了吧。
可能你還不死心,覺得這種寫法看起來有點别扭,有沒有辦法不加 1=1 呀?
呵呵,還真有辦法,MyBatis 開發者也考慮到這一點,是以專門提供了一個 where 語句,就是拿來搞定這個的。
用法如下:
可以看到,where 語句用于格式化輸出,并沒有什麼實質的作用,隻是讓 SQL 語句看起來舒服一點罷了。
choose / when / otherwise 語句其實和 if 語句作用差不多,但是也有一些差別。
我們還是用之前年齡和性别兩個動态條件查詢,來說一說它的用法。
這裡的 choose / when / otherwise 語句的作用其實和 Java 裡的 switch / case / default 語句或者 if / elseif / else 語句差不多。
它和之前的 if 語句的差別,在于一個 choose 語句可以有多個條件判斷分支,每一個 when 語句代表一個條件判斷分支。當有一個 when 語句滿足條件,其他的 when 語句不再執行條件判斷,當所有的 when 都不滿足條件,那麼就選擇預設分支 otherwise。
說了半天,我們還是測試一下,看看效果如何,測試代碼如下:
我現在把兩個條件查詢參數都設定為 null,那麼兩個 when 語句都不滿足條件,最終流程應該選擇 otherwise 分支。
結果果然不出所料。
現在 if 語句和 choose 語句的使用和差別都明白了, 那麼在實際項目開發中該用哪個呢?
如果在 Java 裡你知道何時用 if 語句或 switch / case / default 語句,我相信何時使用 if 語句或 choose / when / otherwise 語句,對你來說絕不是問題。
看到 foreach 語句很容易聯想到 Java 裡 for 語句的增強版 foreach 語句,用法如下:
MyBatis 提供的 foreach 語句主要用來對一個集合進行周遊,通常是用來建構 IN 條件語句,也可用于其他情況下動态拼接 SQL 語句。
我們還是通過一個例子講解 foreach 語句如何使用的,不過我要事先申明有一定難度哈。
假設我有一個需求,就是我的輸入參數是姓名集合(裡面有一堆姓名,如張三、李四、王五),需要從資料庫中依次查詢出對應的使用者資訊。
看到這個需求,如果你的 SQL 還算紮實,應該立馬想到 IN 條件語句,而且腦袋裡立刻浮現出如下 SQL 語句:
如果你腦袋裡一片空白,建議抽空複習一下 SQL。
以上是直接在資料庫裡寫 SQL 語句,要記住需求裡的姓名集合是通過輸入參數傳遞進來的,而不是這裡直接寫死的哈。
首先,我們需要在 UserMapper.java 裡增加一個接口方法,而且方法的參數是姓名集合,如下:
接着,在 UserMapper.xml 裡增加這個接口方法的 SQL 語句映射,如下:
以上可知,foreach 語句有幾個屬性,如下:
collection:表示需要周遊的集合,它的屬性值有三種情況,如下:
如果傳入的是單參數且參數類型是一個 List 的時候,collection 屬性值為 list
如果傳入的是單參數且參數類型是一個 Array 數組的時候,collection 的屬性值為 array
如果傳入的參數是多個的時候,一般需要使用 @param 取别名,collection 屬性值為别名
item:表示每次周遊時生成的對象名
index:表示在疊代過程中,每次疊代到的位置
open:表示開始周遊時要拼接的字元串
separator:表示在每次周遊時兩個對象直接的連接配接字元串
close:表示結束周遊時要拼接的字元串
看到 foreach 語句居然有這麼多屬性,是不是覺得掌握有點難度,這隻是紙老虎而已,别被它吓住了。
你隻需要明白一點,以上 foreach 語句意思無非就是從 List 集合中疊代擷取每個元素,然後再進行 SQL語句拼接,工作原理類似于 Java 的 for 循環動态拼接字元串,如下:
這幾個屬性中,其他屬性值照着填寫即可,collection 屬性值不能亂填,它有三種情況,規則上面已經寫得很清楚了,由于我們隻有一個參數而且集合是 List 類型,是以适合情況一,collection 屬性值應該填寫為 list。
最後,在 MyBatisTest.java 中添加單元測試方法,如下:
執行測試,結果如下:
foreach 語句最終疊代拼接 SQL 語句構成了一條 IN 條件語句,輸入參數 List 集合的元素分别對應三個參數占位符。
我們再仔細想一想,有沒有發現這裡 foreach 語句的作用其實就是批量查詢,等價于三條 SQL 語句如下:
那麼 foreach 可以完成批量查詢,那麼也可以完成批量删除等。
set 語句用于更新操作,功能和 where 語句差不多。
假設我們有一個需求,需要更新某個指定使用者的姓名和使用者名,但是隻有姓名不為 null 才更新
映射接口方法:
我們使用 if 語句就可以輕松搞定,而且用一個 if 就夠了,但是我用了兩個 if 語句,目的是為了講解 set 語句的作用。
單元測試代碼如下:
報文法錯誤,很明顯,執行的 SQL 語句文法不對,user_name 後面多了一個逗号。
怎麼改?你會覺得這還不簡單,把多餘的逗号去掉就可以啦。
但是,你想一想如果隻有第一個 if 語句滿足條件,那麼一樣也會出問題,SQL 語句變為如下:
或者 兩個 if 語句都不滿足條件,那麼 SQL語句變為如下:
結果都會報 SQL 文法錯誤問題。
看來這個問題還有點棘手,這時候就該我們的 set 語句出場啦,如下:
set 語句的作用就是更新操作時自動删除多餘的逗号
當然,還有一個問題 set 語句仍然無法解決,就是如果兩個 if 語句都不滿足條件,即 set 語句後面為空,如下:
trim 元素的主要有兩個功能:
可以在自己包含的内容前加上某些字首,也可以在其後加上某些字尾
prefix 屬性(添加字首)
suffix 屬性(添加字尾)
可以把包含内容的首部某些内容覆寫,即忽略,也可以把尾部的某些内容覆寫,
prefixOverrides 屬性(覆寫首部)
suffixOverrides 屬性(覆寫尾部)
正因為 trim 語句有這樣的功能,trim 語句可以用來實作 where 語句和 set 語句的效果。
trim 語句實作 where 語句效果如下:
prefix="where" 表示在 trim 語句包含的語句前添加字首 where
prefixOverrides="and | or" 表示在 trim 語句包含的語句前出現 and 或 or 則自動忽略
想一想,這不就是之前 where 語句的功能,trim 語句确實可以代替 where 語句。
trim 語句實作 set 語句效果如下:
prefix="set" 表示在 trim 語句包含的語句前添加字首 setsuffixOverrides="," 表示在 trim 語句包含的語句後出現逗号則自動忽略
想一想,這不就是之前 set 語句的功能,trim 語句确實可以代替 set 語句。
以上可知,trim 語句比 set 語句和 where 語句更加靈活,但是使用也更複雜。 它不僅可以實作 set 語句和 where 語句的功能,還可以實作更多内容處理。
實際項目開發中,能用 set 語句或 where 語句盡量不用 trim 語句,可以了解 trim 語句是一個更加底層的内容處理語句。
所有動态 SQL 語句本質都是簡單的對 SQL 語句進行拼接、處理和優化
注意事項:
盡管動态 SQL 可以靈活的拼接 SQL 語句,但是也不要濫用 動态 SQL,盡可能業務邏輯比較相似的,通過條件進行控制。試想一下如果一整張表的所有邏輯全都是一條 SQL 語句,通過各種 if 或者 choose 拼接起來,這并不能代表你很牛逼,隻能代表你很傻逼,因為可讀性和可維護性非常差,出了問題排查起來就會要你的命
對于動态 SQL 根本仍舊是 SQL 的編寫,是以需要具有良好的 SQL 語句編寫能力,動态 SQL 隻是可以讓 SQL 語句更加靈活,并不能解決你 SQL 語句中