引用請注明http://www.cnblogs.com/13590/archive/2013/03/14/2958735.html
摘要:查詢是數據庫SQL語言的核心,本文介紹了通過iBatis.Net對數據庫的簡單查詢、條件查詢、動態查詢和多表查詢。
關鍵詞:iBatis.Net;動態查詢;多表查詢;數據映射
查詢是數據庫SQL語言的核心,SQL語言只提供唯一一個用於數據庫查詢的語句,即SELECT語句。用於表達SQL查詢的SELECT語句是功能最強也是最復雜的SQL語句。而在實際的項目開發過程中,查詢占了一個很大的比重,通常衡量一個框架的好壞也很大程度上取決於該框架對查詢的靈活性和效率。本節介紹在iBatis.Net中提供的數據庫查詢方式。
在上節建立的項目文件中新添加Maps/Test3.xml和Test3.aspx項,分別記錄XML數據映射信息和相應的程序調用信息。
1、簡單查詢
獲取一個表的內容,如獲取DEAN.SYSUSER表的用戶信息,XML數據映射配置信息為:
<selectid="SelectSysuser"resultMap="SysuserResult">
SELECT * FROM DEAN.SYSUSER
</select>
調用代碼為:
protectedvoid Button1_Click(object sender, EventArgs e)
{
//簡單查詢
try
{
ISqlMapper mapper = Mapper.Instance(); //得到ISqlMapper實例
IList<iBatisTest.Domain.Sysuser> plist = mapper.QueryForList<iBatisTest.Domain.Sysuser>("Test3Map.SelectSysuser",null);//調用QueryForList方法
if (plist != null && plist.Count > 0)
{
GridView1.DataSource = plist;
GridView1.DataBind();
}
Label1.Text = "簡單查詢成功";
}
catch (Exception ex)
{
Label1.Text = ex.Message;
}
}
這種查詢結果返回的是整張表的所有記錄,無需傳入查詢參數,在調用QueryForList方法時把參數置為null。考慮到系統的效率,在實際開發中,對記錄數少的小表才使用。
2、條件查詢
根據條件來查詢結果,條件可以是一個或者多個。這種方式在實際查詢中被廣泛使用,是應用最多的一種查詢。如根據登錄用戶名查詢該用戶信息,數據映射配置信息為:
<selectid="SelectSysuserByUserName"parameterClass="string"resultMap="SysuserResult">
SELECT * FROM DEAN.SYSUSER WHERE LOGINNAME=#value#
</select>
iBatis.Net通過使用#或者$符號來占位,即標識參數。在XML數據映射文件的SQL語句中引入參數有2種方式,一種是內聯參數方式,即使用parameterClass,如上面的配置就采用這種方式。一種是參數映射方式,使用parameterMap。采用內聯方式時,允許我們把屬性名稱、屬性類型、空置方式配置在SQL語句中。
使用內聯參數方式時,傳入的參數有3種類型,分別為:
(1)基本參數類型,很多SQL語句在查詢時只接受一個參數,如int、string,使用#value#來引用,這個value是關鍵字,不可變,上面的示例就采用這種方式。也可以采用#keyName#來引用,keyName為鍵名,注意要區分大小寫。
(2)字典類型參數,使用IDictionary類型的對象作為參數。通常可以使用Hashtable。如上面配置可修改為
<select id="SelectSysuserByUserName" parameterClass="System.Collections.IDictionary" resultMap="SysuserResult">
SELECT * FROM DEAN.SYSUSER WHERE LOGINNAME=#LOGINNAME#
</select>
(3)對象類型參數,可以是一個類或者是哈希表Hashtable,在使用哈希表時,某一個鍵值可以是一個列表List類型。
調用代碼如下:
protectedvoid Button2_Click(object sender, EventArgs e)
{
//條件查詢
try
{
string UserName = TextBox1.Text;//獲取查詢參數用戶名
ISqlMapper mapper = Mapper.Instance(); //得到ISqlMapper實例
IList<iBatisTest.Domain.Sysuser> plist = mapper.QueryForList<iBatisTest.Domain.Sysuser>("Test3Map.SelectSysuserByUserName", UserName);//調用QueryForList方法
if (plist != null && plist.Count > 0)
{
GridView1.DataSource = plist;
GridView1.DataBind();
}
Label1.Text = "條件查詢成功";
}
catch (Exception ex)
{
Label1.Text = ex.Message;
}
}
如果采用字典類型參數傳入,只需要把調用中的參數修改為:
Hashtable hash = new Hashtable();//聲明哈希表
hash.Add("LOGINNAME ", TextBox1.Text); //獲取查詢參數用戶名
3、動態查詢
iBatis.Net提供查詢的靈活性主要體現在支持動態查詢上,即可以動態的生成SQL語句。也只有掌握好動態查詢,才能充分的感受iBatis框架所帶來的便捷和高效。這也是很多軟件公司和開發人員選擇該框架的重要原因。
在開發中經常遇到這種查詢,當用戶沒有輸入查詢條件時查詢所有記錄,如果用戶輸入了查詢條件將根據查詢條件進行查詢。比如上面提到的條件查詢,如果沒有輸入用戶名信息將返回所有用戶信息。這個時候就需要用到動態查詢,根據參數值是否為空,生成兩條不同的SQL語句。XML數據映射配置信息為:
<selectid="SelectSysuserDynamic1"parameterClass="System.Collections.IDictionary"resultMap="SysuserResult">
<![CDATA[ SELECT * FROM DEAN.SYSUSER ]]>
<dynamicprepend="WHERE">
<isNotEmptyprepend="AND"property="LOGINNAME">
<![CDATA[ LOGINNAME = #LOGINNAME# ]]>
</isNotEmpty>
</dynamic>
</select>
dynamic元素用來區分SQL語句的動態部分,dynamic是一個可選項,它中間可以包含任意數據的條件元素。上面的配置信息會根據輸入參數LOGINNAME的值是否為空生成兩條SQL語句。如果為空SELECT * FROM DEAN.SYSUSER,如果不為空SELECT * FROM DEAN.SYSUSER WHERE LOGINNAME = #LOGINNAME#。
調用程序代碼為:
protectedvoid Button3_Click(object sender, EventArgs e)
{
//動態查詢1
try
{
Hashtable hash = newHashtable();//聲明哈希表
hash.Add("LOGINNAME", TextBox2.Text); //獲取查詢參數用戶名
ISqlMapper mapper = Mapper.Instance(); //得到ISqlMapper實例
IList<iBatisTest.Domain.Sysuser> plist = mapper.QueryForList<iBatisTest.Domain.Sysuser>("Test3Map.SelectSysuserDynamic1", hash);//調用QueryForList方法
if (plist != null && plist.Count > 0)
{
GridView1.DataSource = plist;
GridView1.DataBind();
}
Label1.Text = "動態查詢1成功";
}
catch (Exception ex)
{
Label1.Text = ex.Message;
}
}
}
在iBatis.Net中,動態查詢的條件元素包含以下幾種:二元條件元素、一元條件元素和其他條件元素:
3.1二元條件元素
將一個屬性值和靜態值或另一個屬性值比較,如果條件為真,元素將被包容在查詢SQL語句中。
二元條件元素的屬性:
perpend——可被覆蓋的SQL語句組成部分,添加在語句的前面,該屬性為可選。
property——是比較的屬性,該屬性為必選。
compareProperty——另一個用於和前者比較的屬性(必選或選擇compareValue屬性)
compareValue——用於比較的值(必選或選擇compareProperty屬性)
二元條件元素為:
<isEqual> |
比較屬性值和靜態值或另一個屬性值是否相等,如果相等則查詢條件有效。如: <isEqual prepend="AND" property="status" compareValue="Y"> MARRIED = 'TRUE' </isEqual> |
<isNotEqual> |
比較屬性值和靜態值或另一個屬性值是否不相等,如果不相等則查詢條件有效。 |
<isGreaterThan> |
比較屬性值是否大於靜態值或另一個屬性值,如果大於則查詢條件有效。如: <isGreaterThan prepend="AND" property="age" compareValue="18"> ADOLESCENT = 'FALSE' </isGreaterThan> |
<isGreaterEqual> |
比較屬性值是否大於等於靜態值或另一個屬性值,如果相等等於則查詢條件有效。 |
<isLessThan> |
比較屬性值是否小於靜態值或另一個屬性值,如果小於則查詢條件有效。 |
<isLessEqual> |
比較屬性值是否小於等於靜態值或另一個屬性值。如: |
二元條件元素多用在數字的區間選擇上,如年齡、價格、面積等選擇上面,也可以用在日期、字符串等類型的比較。如只顯示ID<=10的指定ID的用戶信息,如果輸入值大於10則顯示全部用戶信息。XML數據映射配置信息為:
<selectid="SelectSysuserDynamic2"parameterClass="System.Collections.IDictionary"resultMap="SysuserResult">
<![CDATA[ SELECT * FROM DEAN.SYSUSER ]]>
<dynamicprepend="WHERE">
<isLessEqualprepend="AND"property="USERID"compareValue="10">
USERID = #USERID#
</isLessEqual>
</dynamic>
</select>
調用代碼為:
protectedvoid Button4_Click(object sender, EventArgs e)
{
//動態查詢2:二元條件元素查詢
try
{
Hashtable hash = newHashtable();//聲明哈希表
int ID = 0;
if (!string.IsNullOrWhiteSpace(TextBox3.Text))
{
ID = Convert.ToInt32(TextBox3.Text);
}
hash.Add("USERID", ID); //獲取查詢參數
ISqlMapper mapper = Mapper.Instance(); //得到ISqlMapper實例
IList<iBatisTest.Domain.Sysuser> plist = mapper.QueryForList<iBatisTest.Domain.Sysuser>("Test3Map.SelectSysuserDynamic2", hash);//調用QueryForList方法
if (plist != null && plist.Count > 0)
{
GridView1.DataSource = plist;
GridView1.DataBind();
}
else
{
GridView1.DataSource = null;
GridView1.DataBind();
}
Label1.Text = "動態查詢2成功";
}
catch (Exception ex)
{
Label1.Text = ex.Message;
}
}
3.2一元條件元素
一元條件元素檢查屬性的狀態是否符合特定的條件。即檢查屬性值是否滿足條件,如果滿足則查詢條件有效。
一元條件元素的屬性和二元條件元素一樣,具有prepend和property屬性,其中property為必選屬性。
一元條件元素為:
<isPropertyAvailable> |
檢查是否存在該屬性。 |
<isNotPropertyAvailable> |
檢查是否不存在該屬性。 |
<isNull> |
檢查屬性是否為null。 |
<isNotNull> |
檢查屬性是否不為null。 |
<isEmpty> |
檢查屬性是否為空,屬性的數據類型為Collection、String 時檢查是否為null或空,即是否為""或size() < 1。如: <isNotEmpty prepend="AND" property="firstName" > FIRST_NAME=#firstName# </isNotEmpty> |
<isNotEmpty> |
檢查屬性是否不為空,檢查方式同上。 |
比如下面的配置例子:
<selectid="SelectSysuserDynamic3"resultMap="SysuserResult"parameterClass="System.Collections.IDictionary">
<![CDATA[ SELECT * FROM DEAN.SYSUSER ]]>
<dynamicprepend="WHERE">
<isPropertyAvailableproperty="SEX">
<isNotNullproperty="SEX"prepend="AND">
SEX=#SEX#
</isNotNull>
</isPropertyAvailable>
<isPropertyAvailableproperty="STATUS">
<isNotNullproperty="STATUS"prepend="AND">
STATUS=#STATUS#
</isNotNull>
</isPropertyAvailable>
</dynamic>
</select>
先判斷傳入參數集是否有SEX參數,如果沒有則不執行SEX=#SEX#查詢條件,再判斷該參數是否為null,不為null才執行查詢條件。isPropertyAvailable元素最大的好處是,如果輸入的參數集不包括設置的參數時程序不會報錯,直接跳過該元素設置內容。
3.3其他元素條件
其他元素條件有兩個元素,一個為ParameterPresent,該元素檢查參數對象是否存在,一個為Iterate,該元素為遍歷整個集合。
ParameterPresent元素屬性只有prepend一個屬性,表示可被覆蓋的SQL語句組成部分,添加在語句的前面,為可選屬性。
(2) Iterate:遍歷整個集合元素,為List集合中的元素重復元素體的內容。
Iterate的屬性:
prepend——可被覆蓋的SQL語句組成部分,添加在語句的前面,該屬性為可選。
property——類型為List的用於遍歷的元素屬性,該屬性為必選。
open——整個遍歷內容體開始的字符串,用於定義括號,該屬性為可選。
close ——整個遍歷內容體結束的字符串,用於定義括號,該屬性為可選。
conjunction——每次遍歷內容之間的字符串,用於定義AND或OR,該屬性為可選。
<iterate> |
遍歷類型為List的元素。如: <iterate prepend="AND" property="UserNameList" open="(" close=")" conjunction="OR"> username=#UserNameList[]# </iterate> 注意:使用<iterate>時,在List元素名后面包括方括號[]非常重要,方括號[]將對象標記為List,以防解析器簡單地將List輸出成String。 |
Iterate元素在生成sql語句時,標簽中的內容是循環生成的,如上面的例子將會生成語句:(username=xxx1 or username=xxx2 or username=xxx 3)。該元素也經常用來動態生成In查詢條件,如id in (xx1,xx2,xx3,.....),括號中的(包括括號)都由該元素標簽生成。
比如下面的配置例子:
<selectid="SelectSysuserDynamic4"resultMap="SysuserResult"parameterClass="System.Collections.IDictionary">
<![CDATA[ SELECT * FROM DEAN.SYSUSER ]]>
<dynamicprepend=" WHERE">
<isPropertyAvailableproperty="SEX">
<isNotNullproperty="SEX"prepend="AND">
SEX=#SEX#
</isNotNull>
</isPropertyAvailable>
<isNotNullprepend="And"property="USERIDLIST">
USERID in
<iterateproperty="USERIDLIST"open="("close=")"conjunction=",">
#USERIDLIST[]#
</iterate>
</isNotNull>
</dynamic>
</select>
調用代碼為:
protectedvoid Button6_Click(object sender, EventArgs e)
{
//動態查詢2:其它元素條件,Iterate
try
{
Hashtable hash = newHashtable();//聲明哈希表
string sex = "男";
hash.Add("SEX", sex);
List<int> IDS = newList<int>();//聲明List對象
IDS.Add(1);
IDS.Add(2);
IDS.Add(3);
hash.Add("USERIDLIST", IDS);
ISqlMapper mapper = Mapper.Instance(); //得到ISqlMapper實例
IList<iBatisTest.Domain.Sysuser> plist = mapper.QueryForList<iBatisTest.Domain.Sysuser>("Test3Map.SelectSysuserDynamic4", hash);//調用QueryForList方法
if (plist != null && plist.Count > 0)
{
GridView1.DataSource = plist;
GridView1.DataBind();
}
else
{
GridView1.DataSource = null;
GridView1.DataBind();
}
Label1.Text = "動態查詢4成功";
}
catch (Exception ex)
{
Label1.Text = ex.Message;
}
}
系統會根據配置信息動態的生成In查詢條件,最終動態生成的SQL語句為:SELECT * FROM DEAN.SYSUSER AND SEX='男' And USERID in (1,2,3)。
4、多表查詢
前面講到的示例都是從一個表中查詢記錄,獲取的結果也是單個對象。事實上在程序開發中,經常需要對多個表進行組合查詢,返回的結果也是復雜對象。如查詢用戶權限信息,就需要關聯用戶表和權限表。
向數據庫添加系統權限表SysUserRight,腳本如下:
CREATE TABLE DEAN.SYSUSERRIGHT
(
ID NUMBER(10,0) NOT NULL ENABLE,
USERID NUMBER(10,0) NOT NULL ENABLE,
RIGHTID NUMBER(10,0) NOT NULL ENABLE,
constraint PK_SYSUSERRIGHT primary key (ID)
);
comment on column DEAN.SYSUSERRIGHT.ID is 'ID';
comment on column DEAN.SYSUSERRIGHT.USERID is '用戶ID';
comment on column DEAN.SYSUSERRIGHT.RIGHTID is '權限ID';
有兩種方式來處理這種多表查詢,一種是參照單表查詢,根據返回結果定制一個新類,或者直接設置返回參數為Hashtable表。如:
<selectid="MultiTable1"resultClass="Hashtable" >
SELECT A.*,B.RIGHTID FROM DEAN.SYSUSER A,DEAN.SYSUSERRIGHT B WHERE A.USERID=B.USERID
</select>
通過用戶ID(USERID)關聯用戶表(SYSUSER)和系統權限表(SYSUSERRIGHT)查詢出用戶信息及對應的權限信息,直接返回一個Hashtable表,記錄了相應的信息。
第二種方式是利用iBatis的復雜屬性來實現,在Sysuser類新增一個屬性:
///<summary>
///多表查詢新增權限屬性
///</summary>
privateint _rightid;
publicint Rightid
{
get { return _rightid; }
set {_rightid = value;}
}
修改配置文件信息,在resultMaps部分增加一個結果映射信息,唯一號為UserRightResult,它繼承於Test3Map.SysuserResult結果映射,增加的配置信息如下:
<resultMapid="UserRightResult"class="Sysuser"extends="Test3Map.SysuserResult">
<resultproperty="<spansstyle="color:blue; background-color:white">Rightid"column="USERID=USERID"select="Test3Map.SelectSysuserRight" />
</resultMap>
在statements節加入如下信息:
<statementid="SelectSysuserRight"parameterClass="int"resultClass="int">
SELECT RIGHTID FROM DEAN.SYSUSERRIGHT WHERE USERID= #USERID#
</statement>
<selectid="MultiTable2"parameterClass="int"resultMap="UserRightResult">
SELECT * FROM DEAN.SYSUSER ORDER BY USERID
</select>
通過XML配置文件中resultMap的result使用"select"進行一種迭代查詢,也就是將<result property="Rightid" column="USERID=USERID" select="Test3Map.SelectSysuserRight" />中column指定的一項或多項作為參數(USERID=USERID),傳入並執行指定的select語句SelectSysuserRight,並將查詢結果賦給property=" Rightid ",從而實現多表查詢。
該例子中實現的是1:1的關系查詢,如果是1:n的關系查詢,只需要Sysuser類增加的屬性修改為IList類型。Statements節點"SelectSysuserRight"的返回類修改為resultClass=" SysuserRightResult"。
通過iBatis復雜屬性,可以非常方便的實現多表查詢,但這個方法給我們帶來便利的同時,也帶來了兩個問題。首先,創建包含大量對象的列表可能會消耗大量的內存。其次,這種方法會導致數據庫的I/O問題,其原因就是所謂的"N+1"查詢現象。對於主從表(也稱為父子表)的查詢,特別容易產生N+1查詢問題,N+1查詢問題是由於試圖加載多個父記錄(比如User)的子記錄(Right)而引起的。在查詢父記錄時,只需要1條語句,假設返回N條記錄,那么就需要再執行N條語句來查詢子記錄,引發所謂的"N+1查詢"。
解決N+1查詢問題可以通過延遲加載(lazy loading)來實現,它將加載過程打散為一些更小的過程。在父子表查詢過程中,對子表的查詢往往不需要和父表一起加載,例如,系統的用戶管理,打開一個用戶信息頁面時顯示的是用戶信息列表,當點擊一個用戶時,才需要加載該用戶的權限信息。這種情況就特別適合使用延遲加載,每次都僅查詢一個列表。使用延遲加載的時候還需要特別注意,使用的動態代理的對象的所有方法和屬性都必須是virtual類型。
要實現延遲加載,只需要在配置文件里面加入lazyLoad="true"屬性就可以了。通過延遲加載,它能提高查詢的效率,但並沒有真正解決數據庫I/O問題,在最壞的情況下,它對數據庫的訪問次數與非延遲加載的時候是一樣的。如何真正解決N+1查詢問題呢?iBatis提供了連接語句(join)方式來完全避免N+1查詢的出現。
修改配置文件信息,在resultMaps部分增加如下配置:
<resultMapid="SysuserRightResult"class="SysuserRight">
<resultproperty="ID"column="ID"/>
<resultproperty="Userid"column="USERID" />
<resultproperty="Rightid"column="RIGHTID"/>
</resultMap>
<resultMapid="SysuserJoinResult"class="SysuserJoin"extends="Test3Map.SysuserResult"groupBy="Userid">
<resultproperty="RightList"resultMapping="Test3Map.SysuserRightResult" />
</resultMap>
這個配置使用了relultMap的resultMapping屬性,該屬性用在如果一個數據類的屬性本身不是基元數據類型,而是一個復雜數據類型的場景。這個時候就不能用一個簡單的result元素來表示,必須給他一個完整的resultMap。resultMapping的值指明RightList屬性由結果映射集SysuserRightResult所表示的復雜數據類型表示。因為用戶和權限信息是一對多的關系,在主表的結果映射上加入groupBy="Userid"屬性。
注意用戶類SysuserJoin的權限屬性RightList的定義,它是由權限類SysuserRight組成的一個列表。RightList定義如下:
///多表查詢新增權限列表屬性
privateIList<SysuserRight> _rightlist;
publicIList <SysuserRight> RightList
{
get { return _rightlist; }
set { _rightlist = value; }
}
在statements節加入如下信息
<selectid="MultiTable3"resultMap="SysuserJoinResult" >
select A.*,B.* FROM DEAN.SYSUSER A LEFT JOIN DEAN.SYSUSERRIGHT B ON A.USERID=B.USERID </select>
再通過程序的調用,就不會出現N+1查詢問題。
5、結束語
以上程序在VS2012(C#)+Oracle11gR2+Win7(64位)調試通過,附件的例子程序中有詳細的配置信息和程序調用代碼。