[ 本文原創發表於cnblogs : 布藍燈 ]
近來工作中用到了 ibatis 技術,主要用來完成動態條件的查詢,深感這種一勞永逸的書寫方式確實很genius。不過因為是在使用的過程中照貓畫虎,沒有深入系統地去研究,所以這幾天遇到了一個很棘手的問題。在網上查了很久也沒有太清楚解決辦法,后來終於找到了一篇和我的問題很類似的介紹,在此要多謝原作者的 ibatis傳入數組或List類型參數小結。
之前使用 ibatis 來寫SQL查詢語句,無非就是 WHERE 后的條件是並聯的。也就是說,一些動態的條件用 AND 相連。下面舉一個例子
1 <select id="SelectEnergyCost" parameterClass="Hashtable" resultClass="EnergyCostData">
2 <![CDATA[
3 SELECT * FROM UNITCOMP 4 ]]>
5 <dynamic prepend="WHERE">
6 <isParameterPresent>
7 <isNotEmpty prepend="AND" property="DATEFROM" >
8 <![CDATA[
9 DATETIME >= TO_DATE( #DATEFROM#,'YYYY-MM-DD HH24:MI:SS') 10 ]]>
11 </isNotEmpty>
12 <isNotEmpty prepend="AND" property="DATETO" >
13 <![CDATA[
14 DATETIME <= TO_DATE( #DATETO#,'YYYY-MM-DD HH24:MI:SS') 15 ]]>
16 </isNotEmpty>
17 <isNotEmpty prepend="AND" property="IGROUPID" >
18 IGROUPID = #IGROUPID# 19 </isNotEmpty >
20 <isNotEmpty prepend="AND" property="CONDITION1" >
21 CONDITION1 LIKE '%' || #CONDITION1# || '%' 22 </isNotEmpty >
23 <isNotEmpty prepend="AND" property="CONDITION2" >
24 CONDITION2 LIKE '%' || #CONDITION2# || '%' 25 </isNotEmpty >
26 <isNotEmpty prepend="AND" property="CONDITION3" >
27 CONDITION3 LIKE '%' || #CONDITION3# || '%' 28 </isNotEmpty >
29 </isParameterPresent>
30 </dynamic>
31 ORDER BY DATETIME DESC 32 </select>
(涉及工作內容,部分地方命名做了一些通用化處理)
簡單說一下上面這段,這樣即使對 ibatis 不熟悉的人也能更清楚看懂。
首先是<select>標簽,parameterClass是入參,也就是接下來的查詢語句中將要用到的各個字段的值,resultClass是輸出,也就是輸出的查詢結果。輸入輸出都可以是很靈活的格式,可以只是一個整型變量,也可以是一個數據集。在我的例子中,輸出是字段與值相對應的Hashtable,輸出則是一個數據集,字段與所查詢的表一致。
之后就可以直接寫SQL語句了。假如不涉及到動態條件,那么就可以直接寫完整的SQL語句。因為程序本身是xml格式,為了避免SQL語句和xml的字符沖突,可以用“<![CDATA[”和“]]>”把SQL括起來。
如果涉及到了動態條件,就要用到<dynamic>標簽。ibatis 中可選的標簽有很多,我這里用的是<isNotEmpty>,主要也是因為平台原有的一些語句也都是用的這個,我就照着搬下來了。具體其他標簽有什么區別,大家如果有需要,可以自行研究。<isNotEmpty>標簽的屬性看一眼也很清晰,prepend是指在接下來的語句之前要加這個詞,property是入參。因為是動態的,所以當入參為空的時候,prepend的內容會自動省略,所以入參為空時,在這個<isNotEmpty>中的所有內容都不會添加到語句中,包括prepend。
有多個條件就添加多個<isNotEmpty>就可以了。接下來的SQL語句就正常寫,需要賦值為入參的地方用“#入參名#”。這里除了#還可以用$,我沒有具體研究過,區別似乎是二者對於入參的數據類型要求不一樣,並且$的安全性較低。感興趣可以深入研究一下。
所以上面這段程序拼接之后就是:
SELECT * FROM UNITCOMP WHERE DATETIME >= TO_DATE( #DATEFROM#,'YYYY-MM-DD HH24:MI:SS' ) AND DATETIME <= TO_DATE( #DATETO#,'YYYY-MM-DD HH24:MI:SS' )
AND IGROUPID = #IGROUPID# AND CONDITION1 LIKE '%#CONDITION1#%' AND CONDITION2 LIKE '%#CONDITION2#%' AND CONDITION3 LIKE '%#CONDITION3#%'
那么接下來就是我遇到的問題了。在上面的查詢語句中,我們需要IGROUPID傳進來一個固定的值。但是如果遇到IGROUPID可以取多個值的話,該怎么辦呢?也就是下面這段SQL:
SELECT * FROM UNITCOMP WHERE DATETIME >= TO_DATE( #DATEFROM#,'YYYY-MM-DD HH24:MI:SS' ) AND DATETIME <= TO_DATE( #DATETO#,'YYYY-MM-DD HH24:MI:SS' ) AND IGROUPID IN ( #IGROUPID1#, #IGROUPID2# ) AND CONDITION1 LIKE '%#CONDITION1#%' AND CONDITION2 LIKE '%#CONDITION2#%' AND CONDITION3 LIKE '%#CONDITION3#%'
重點看IGROUPID這一段,我們能夠想到的解決辦法可能有這么幾種:
1、IGROUPID = IGROUPID1 OR IGROUPID = IGROUPID3 OR IGROUPID = IGROUPID3 ...
2、IGROUPID IN ( IGROUPID1, IGROUPID2, ... )
3、IGROUPID IN ( IGROUPIDS )。其中 IGROUPIDS為IGROUPID1, IGROUPID2, ... 的集合
通過比較,當然是第三種方式最優。既然是動態語句,我們就希望它的擴展性能達到最佳,在前台只需要定義了IGROUPS集合,不管集合中有幾個元素,我們都可以一次性傳進來。
但在解決問題的過程中,以上幾種方式我都嘗試過,都沒有很好地成功。實際操作的時候會出現一些問題,不如想象中那么清晰明了,比如第一種,雖然傳參數簡單了,但是這些OR語句拼接好以后外面還需要加一對括號,因為這個條件是在復合條件中,它還需要考慮到與語句其他部分的銜接。而動態添加括號又會很麻煩。
在查找資料的過程中,我發現了<iterate>這個標簽。通過不斷迭代,就可以實現我們所需要的功能。問題在於,這種方法的格式我不太清楚是怎樣的,包括入參應該以什么樣的形式傳入。過程就不廢話了,通過看開頭提到的文章,我最終還是解決了這個問題,下面先直接給出結果:
1 <select id="SelectEnergyCost2" parameterClass="Hashtable" resultClass="EnergyCostData"> 2 <![CDATA[ 3 SELECT * FROM UNITCOMP 4 ]]> 5 <dynamic prepend="WHERE"> 6 <isParameterPresent> 7 <isNotEmpty prepend="AND" property="DATEFROM" > 8 <![CDATA[ 9 DATETIME >= TO_DATE( #DATEFROM#,'YYYY-MM-DD HH24:MI:SS') 10 ]]> 11 </isNotEmpty> 12 <isNotEmpty prepend="AND" property="DATETO" > 13 <![CDATA[ 14 DATETIME <= TO_DATE( #DATETO#,'YYYY-MM-DD HH24:MI:SS') 15 ]]> 16 </isNotEmpty> 17 <isNotEmpty prepend="AND" property="GROUPIDS"> 18 IGROUPID IN 19 <iterate open="(" close=")" conjunction="," property="GROUPIDS" > 20 $GROUPIDS[]$ 21 </iterate > 22 </isNotEmpty> 23 <isNotEmpty prepend="AND" property="CONDITION1" > 24 CONDITION1 LIKE '%' || #CONDITION1# || '%' 25 </isNotEmpty > 26 <isNotEmpty prepend="AND" property="CONDITION2" > 27 CONDITION2 LIKE '%' || #CONDITION2# || '%' 28 </isNotEmpty > 29 <isNotEmpty prepend="AND" property="CONDITION3" > 30 CONDITION3 LIKE '%' || #CONDITION3# || '%' 31 </isNotEmpty > 32 </isParameterPresent> 33 </dynamic> 34 ORDER BY DATETIME DESC 35 </select>
重點看IGROUPID這一部分。<iterate>就相當於一個循環語句,首先要定義循環部分開頭和結尾,用open和close屬性分別定義為“(”和“)”,連接符用conjunction屬性定義為“,”,最后入參是GROUPIDS。語句中同樣還是用#GROUPIDS#來接收入參。
那么這里的入參究竟是什么呢?我們自然能想到它要么是一個數組,要么是一個集合。在 ibatis 的規定里,入參必須是array或List<>,也就是數組。所以在語句中,接收入參的部分要是一個能夠接收數組的形式。在這里我嘗試了很久,最后發現,不能直接用GROUPIDS,而是要寫GROUPIDS[]。這樣就會在每一個迭代循環中,分別只用數組的一個元素來賦值。至於為什么用$而非#,我沒有嘗試,大家可以自己試一下#有沒有問題。
xml這部分這樣就解決了。前台的代碼相對也很簡單,只要定義一個數組放進Hashtable就可以了。我采取的是以下的形式(C#),大家可以當做參考。
1 Hashtable ht = new Hashtable(); 2 int iGroupID = int.Parse(this.ddlcGroup.SelectedValue); 3 List<int> ilGroupIDs = new List<int>(); 4 switch (iGroupID) 5 { 6 case 1 : 7 { 8 ilGroupIDs.Add(10001); 9 ilGroupIDs.Add(10002); 10 break; 11 } 12 case 2 : 13 { 14 ilGroupIDs.Add(10003); 15 ilGroupIDs.Add(10004); 16 break; 17 } 18 case 3 : 19 { 20 ilGroupIDs.Add(10005); 21 ilGroupIDs.Add(10006); 22 ilGroupIDs.Add(10007); 23 break; 24 } 25 default : 26 break; 27 } 28 ht.Add("GROUPIDS", ilGroupIDs.ToArray()); 29 ht.Add("DATEFROM", this.txtDateFrom.Text); 30 ht.Add("DATETO", this.txtDateTo.Text);
其中第28行,把數組放進Hashtable的時候,因為ilGroupIDs本身就是一個數組了,所以可能不需要.ToArray()方法,但是我沒有嘗試。感興趣的朋友可以自己深入探討這其中的一些細節。
通過上面的示例,就可以在 ibatis 中完成復核條件的查詢。