轉載請注明 http://www.cnblogs.com/13590/archive/2013/03/01/2938126.html
摘要:本文探討了iBatis.Net框架的XML數據映射文件各配置節點的含義,並通過CRUD四種對數據庫的操作講解了如何配置數據映射文件和調用方法。
關鍵詞:iBatis.Net;XML;SQL Maps;數據映射
上一節介紹了iBatis.Net的基本情況和運行原理,運行環境中各參數的配置情況。並通過一個實例項目進行了說明。
1 數據映射基礎
SQL Maps是這個iBatis.Net框架中最重要的部分,而SQL Maps的核心就在於XML數據映射文件(Data Map XML File)。在XML數據映射文件里可以定義包括要執行各種SQL語句、存儲過程、輸入參數映射、返回結果映射、緩存機制,並且能通過幾種相對比較復雜的配置實現對象之間的關聯關系和延遲加載。這也是iBatis.Net區別其它ORM框架而具備更靈活性,更高性能的關鍵所在。
配置文件可以寫得很簡單,也可以很復雜。復雜配置文件往往出於更好的設計,更好性能,更好擴展性方面的目的。一個XML數據映射文件主要分為兩個部分:模塊配置和語句配置。
2 模塊配置
2.1 Type Alias節點
定義一個XML數據映射文件中的別名,其好處就是避免過長變量值的反復書寫,比如通過typeAlias節點為類"iBatisTest.Domain.Sysuser"定義了一個別名"Sysuser",這樣在這個數據映射文件中的其他部分,需要引用"iBatisTest.Domain.Sysuser"類時,只需以其別名替代即可,和SqlMap.config配置文件里面的Alias節點配置一樣。定義如:
<alias><typeAlias alias="Sysuser" type="iBatisTest.Domain.Sysuser,iBatisTest" /></alias>
2.2 cacheModel節點
緩存機制是程序開發中經常討論的一個話題,在實際的系統開發的過程中,總會存在着這樣一類數據,它們更新頻率很低,然而使用的頻率卻非常之高。為了提高系統性能,通常將此類數據裝入緩存。iBatis.Net也有自己的緩存系機制,它通過cacheModel節點來配置,具體的定義如下:
<cacheModel type="LRU" id="Sysuser-cache" readOnly="true" serialize="false">
<flushInterval hours="24"/>
<flushOnExecute statement="SysuserMap. UpdateSysuser "/>
<property name="CacheSize" value="100"/>
</cacheModel>
type:緩存的類型,iBatis.Net中有4種類型,分別為MEMORY、LRU、FIFO、OSCACHE。
其中MEMORY是內存緩存,LRU是使用最近最少使用策略,FIFO是使用先進先出策略,OSCACHE是通過第三方的緩存插件來實現。
id:是cacheModel的一個標識,標識該緩存的名字,供后面設置使用。
readOnly:指緩存的數據對象是只讀還是可讀寫,默認為"true",即只讀,這里的只讀並不是意味着數據對象一旦放入緩存中就無法再對數據進行修改。而是當數據對象發生變化的時候,如數據對象的某個屬性發生了變化,則此數據對象就將被從緩存中廢除,下次需要需重新從數據庫讀取數據,構造新的數據對象。而readOnly="false"則意味着緩存中的數據對象可更新。
serialize:是否從緩存中讀取同一個對象,還是對象的副本。該參數只有在readOnly為false的情況下才有效, 因為緩存是只讀的,那么為不同會話返回的對象肯定是一個,只有在緩存是可讀寫的時候,才需要為每個會話返回對象的副本。
flushInterval:指定緩存自動刷新的時間,可以為hours、minutes、seconds和milliseconds。需要注意的是,這個間隔時間不是時間到了,在緩存里的信息會自動刷新,而是在間隔時間過后,下次查詢將不會從緩存中去取值,而會用SQL去查詢,同時將查詢的結果更新緩存的值。
flushOnExecute:指定在發生哪些操作時,更新緩存。
property:針對cacheModel的額外的一些屬性配置,不同type的cacheModel將會有自己專有的一些property配置,如:
FIFO: <property name="CacheSize" value="100" />
LRU: <property name="CacheSize" value="100" />
MEMORY: <property name="Type" value="WEAK" />
當配置好cacheModel之后就可以在后面的語句中使用了,如:
﹤statement id="SelectSysuser" resultClass="Sysuser" cacheModel="Sysuser-cache"﹥
SELECT * FROM DEAN.SYSUSER
﹤/statement﹥
2.3 resultMap節點
resultMap從字面理解就是結果集的映射,它將返回的記錄與實體對象進行映射,它屬於直接映射。如果查詢出來的數據集的字段與屬性和實體類一致時,我們常用resultClass來替代,它屬於隱身映射。通常resultMap比resultClass性能要高。
在使用resultMap時需要注意,如果在resultMap中給出的配置字段,但是返回的數據集卻沒有返回這個字段,那程序將出拋出異常。相反的,如果返回了一些字段,卻沒有在resultMap給出配置定義的話,那么那些字段將不會被處理而不會給出任何的提示,相當沒有查詢出這些字段。具體的定義例子如下:
<resultMaps>
<resultMap id="SysuserResult" class="Sysuser">
<result property="Userid" column="USERID" />
<result property="Password" column="PASSWORD" />
<result property="Loginname" column="LOGINNAME" />
<result property="Sex" column="SEX"/>
<result property="Birthday" column="BIRTHDAY"/>
<result property="Idcard" column="IDCARD" />
<result property="Officephone" column="OFFICEPHONE" />
<result property="Familyphone" column="FAMILYPHONE" />
<result property="Mobilephone" column="MOBILEPHONE"/>
<result property="Email" column="EMAIL"/>
<result property="Address" column="ADDRESS"/>
<result property="Zipcode" column="ZIPCODE"/>
<result property="Remark" column="REMARK" />
<result property="Status" column="STATUS" />
<result property="Registerdate" column="REGISTERDATE" />
</resultMap>
</resultMaps>
該參數也是查詢語句特有的配置項,因為insert,update,delete三種語句的操作是不會返回數據結果集的,也就沒有resultMap和resultClass。如果在一個語句配置中同時指定了resultMap和resultClass屬性的話,將會優先使用resultMap。同時result Map的使用也是一個實現對象復雜查詢功能的重要手段,如:result map的繼承,對象的1..1、1..N關系查詢。
2.4 ParameterMap節點
參數映射的配置,它是被用來向一個語句提供所需參數的配置。該參數配置節點和resultMaps節點類似。
3 語句配置
語句配置(Mapped Statements):顧名思義就是映射的語句聲明。它是XML數據映射文件的核心,在iBatis.Net框架中真正和數據庫打交道的被執行的SQL語句(或存儲過程)都必須在這里被顯式聲明。語句配置可以包含有:statement、select、insert、update、delete和procedure這6種不同的語句類型。其中statement可以包含所有類型的SQL語句(存儲過程),它是一個泛泛的語句配置,沒特別明確的職責,相反,其它5種類型的語句配置就是專門負責各種不同的SQL語句。下面這張表列出了各種類型的語句的不同職責和調用方法。
Statement類型 |
屬性 |
子元素 |
方法 |
<statement> |
id parameterClass resultClass parameterMap resultMap cacheModel |
所有動態元素 |
update delete 所有的查詢方法 |
<insert> |
id parameterClass parameterMap |
所有的動態元素 <selectKey> |
insert update delete |
<update> |
id parameterClass parameterMap |
所有的動態元素 |
insert update delete |
<delete> |
id parameterClass parameterMap |
所有的動態元素 |
insert update delete |
<select> |
id parameterClass resultClass parameterMap resultMap cacheModel |
所有的動態元素 |
所有的查詢方法 |
<procedure> |
id parameterClass resultClass parameterMap resultMap |
所有的動態元素 |
insert update delete 所有的查詢方法 |
應用系統操作數據庫數據一般有CRUD四種方式,即增加(Create)、查詢(Retrieve,重新得到數據)、更新(Update)和刪除(Delete)。下面詳細介紹一下這四種情況的SQL語句配置及調用方法。
首先在Oracle數據庫中新建表DEAN.SYSUSER。表結構如圖1
圖1:SYSUSER表結構
增加實體類iBatisTest.Domain.Sysuser,該實體類和表DEAN.SYSUSER對應。Sysuser類代碼如下:
using System;
namespace iBatisTest.Domain
{
[Serializable]
public sealed class Sysuser
{
#region 私有成員定義
private Int32 _userid;
private string _password;
private string _loginname;
private string _sex;
private DateTime _birthday;
private string _idcard;
private string _officephone;
private string _familyphone;
private string _mobilephone;
private string _email;
private string _address;
private string _zipcode;
private string _remark;
private string _status;
private DateTime _registerdate;
#endregion 私有成員定義
#region 定義默認類結構函數
public Sysuser()
{
_userid = 0;
_password = null;
_loginname = null;
_sex = null;
_birthday = new DateTime();
_idcard = null;
_officephone = null;
_familyphone = null;
_mobilephone = null;
_email = null;
_address = null;
_zipcode = null;
_remark = null;
_status = null;
_registerdate = new DateTime();
}
#endregion 定義默認類結構函數
#region 定義公有屬性
/// <summary>
/// 登錄ID
/// </summary>
public Int32 Userid
{
get { return _userid; }
set { _userid = value; }
}
/// <summary>
/// 登錄密碼
/// </summary>
public string Password
{
get { return _password; }
set
{
if( value!= null && value.Length > 40)
throw new ArgumentOutOfRangeException("密碼值錯誤", value, value.ToString());
_password = value;
}
}
/// <summary>
/// 登錄用戶名
/// </summary>
public string Loginname
{
get { return _loginname; }
set
{
if( value!= null && value.Length > 40)
throw new ArgumentOutOfRangeException("登錄用戶名值錯誤", value, value.ToString());
_loginname = value;
}
}
/// <summary>
/// 性別
/// </summary>
public string Sex
{
get { return _sex; }
set
{
if( value!= null && value.Length > 40)
throw new ArgumentOutOfRangeException("性別值錯誤", value, value.ToString());
_sex = value;
}
}
/// <summary>
/// 出生日期
/// </summary>
public DateTime Birthday
{
get { return _birthday; }
set { _birthday = value; }
}
/// <summary>
/// 身份證號
/// </summary>
public string Idcard
{
get { return _idcard; }
set
{
if( value!= null && value.Length > 18)
throw new ArgumentOutOfRangeException("身份證號值錯誤", value, value.ToString());
_idcard = value;
}
}
/// <summary>
/// 辦公電話
/// </summary>
public string Officephone
{
get { return _officephone; }
set
{
if( value!= null && value.Length > 40)
throw new ArgumentOutOfRangeException("辦公電話值錯誤", value, value.ToString());
_officephone = value;
}
}
/// <summary>
/// 家庭電話
/// </summary>
public string Familyphone
{
get { return _familyphone; }
set
{
if( value!= null && value.Length > 40)
throw new ArgumentOutOfRangeException("家庭電話值錯誤", value, value.ToString());
_familyphone = value;
}
}
/// <summary>
/// 手機
/// </summary>
public string Mobilephone
{
get { return _mobilephone; }
set
{
if( value!= null && value.Length > 40)
throw new ArgumentOutOfRangeException("手機號值錯誤", value, value.ToString());
_mobilephone = value;
}
}
/// <summary>
/// </summary>
public string Email
{
get { return _email; }
set
{
if( value!= null && value.Length > 100)
throw new ArgumentOutOfRangeException("Email值錯誤", value, value.ToString());
_email = value;
}
}
/// <summary>
/// 通訊地址
/// </summary>
public string Address
{
get { return _address; }
set
{
if( value!= null && value.Length > 200)
throw new ArgumentOutOfRangeException("通訊地址值錯誤", value, value.ToString());
_address = value;
}
}
/// <summary>
/// 郵編
/// </summary>
public string Zipcode
{
get { return _zipcode; }
set
{
if( value!= null && value.Length > 20)
throw new ArgumentOutOfRangeException("郵編值錯誤", value, value.ToString());
_zipcode = value;
}
}
/// <summary>
/// 備注
/// </summary>
public string Remark
{
get { return _remark; }
set
{
if( value!= null && value.Length > 200)
throw new ArgumentOutOfRangeException("備注值錯誤", value, value.ToString());
_remark = value;
}
}
/// <summary>
/// 狀態
/// </summary>
public string Status
{
get { return _status; }
set
{
if( value!= null && value.Length > 20)
throw new ArgumentOutOfRangeException("狀態值錯誤", value, value.ToString());
_status = value;
}
}
/// <summary>
/// 注冊時間
/// </summary>
public DateTime Registerdate
{
get { return _registerdate; }
set {_registerdate = value; }
}
#endregion 定義公有屬性
}
}
(1)添加
添加也即插入,使用的SQL語句是insert。XML數據映射配置信息如下:
<insert id="InsertSysuser" parameterClass="Sysuser">
INSERT INTO DEAN.SYSUSER
(USERID,PASSWORD,LOGINNAME,SEX,BIRTHDAY,IDCARD,OFFICEPHONE,FAMILYPHONE,MOBILEPHONE,EMAIL,ADDRESS,ZIPCODE,REMARK,STATUS)
VALUES (#Userid#,#Password#,#Loginname#,#Sex#,#Birthday#,#Idcard#,#Officephone#,#Familyphone#,#Mobilephone#,#Email#,#Address#,#Zipcode#,#Remark#,#Status#)
</insert>
insert標簽表面該語句是插入語句,id為該配置語句的標識,在后面的調用中通過該標識引用這個語句。變量用#號分隔,如"#Userid#"在運行期會由傳入的Sysuser對象的Userid屬性填充,注意變量名需區分大小寫。調用代碼如下:
protected void BtnAddUser_Click(object sender, EventArgs e)
{
try
{
iBatisTest.Domain.Sysuser model = new iBatisTest.Domain.Sysuser();//實例化Sysuser對象
model.Userid = 1;//Userid為主鍵,多次添加需修改該值
model.Password = "123";
model.Loginname ="dean";
model.Sex = "男";
model.Birthday = Convert.ToDateTime("1980-01-01");
model.Idcard ="510200198001014200";
model.Officephone="86279528";
model.Familyphone ="86279528";
model.Mobilephone = "13880980000";
model.Email ="476408321@qq.com";
model.Address = "四川成都";
model.Zipcode = null;
model.Remark = null;
model.Status = "有效";
ISqlMapper mapper = Mapper.Instance(); //得到ISqlMapper實例
mapper.Insert("SysuserMap.InsertSysuser", model);//調用Insert方法
Label1.Text = "添加用戶成功";
}
catch (Exception ex)
{
Label1.Text = ex.Message;
}
}
調用程序先實例化Sysuser對象並賦值,再得到ISqlMapper的實例,調用該實例的Insert方法。Insert方法有兩個參數,分別是語句名稱和參數對象。語句名稱就是數據映射文件里面的配置語句id,參數對象是一個object對象,可以傳入類、單個值或者哈希表等。
編譯程序,點擊"添加用戶"按鈕運行調用程序,系統會成功的向數據庫SYSUSER表新增一條記錄。查詢數據庫結果如圖2所示:
圖2:新增數據后數據庫結果
(2)更新
更新修改操作使用的SQL語句為update,XML數據映射文件配置信息為:
<update id="UpdateSysuser" parameterClass="Sysuser">
UPDATE SYSUSER
SET PASSWORD=#Password#,LOGINNAME=#Loginname#,SEX=#Sex#,BIRTHDAY=#Birthday#,IDCARD=#Idcard#,OFFICEPHONE=#Officephone#,FAMILYPHONE=#Familyphone#,MOBILEPHONE=#Mobilephone#,EMAIL=#Email#,ADDRESS=#Address#,ZIPCODE=#Zipcode#,REMARK=#Remark#,STATUS=#Status#
WHERE USERID = #Userid#
</update>
調用代碼為:
protected void BtnUpdateUser_Click(object sender, EventArgs e)
{
try
{
iBatisTest.Domain.Sysuser model = new iBatisTest.Domain.Sysuser();
model.Userid = 1;//修改Userid為1的用戶信息
model.Password = "1234";
model.Loginname = "deanu";
model.Sex = "男";
model.Birthday = Convert.ToDateTime("1970-01-01");
model.Idcard = "310200198001014200";
model.Officephone = "76279528";
model.Familyphone = "76279528";
model.Mobilephone = "13880000000";
model.Email = "1@qq.com";
model.Address = null;
model.Zipcode = "610000";
model.Remark = "修改ID為1的用戶";
model.Status = "有效";
ISqlMapper mapper = Mapper.Instance(); //得到ISqlMapper實例
mapper.Update("SysuserMap.UpdateSysuser", model);//調用Update方法
Label1.Text = "修改用戶成功";
}
catch (Exception ex)
{
Label1.Text = ex.Message;
}
}
(3)刪除
刪除操作使用的SQL語句為delete,XML數據映射文件配置信息為:
<delete id="DeleteSysuser" parameterClass="int">
DELETE FROM SYSUSER WHERE USERID = #Userid#
</delete>
由於刪除操作是根據主鍵進行處理的,傳入參數為整形值,因此在parameterClass直接為int。調用代碼為:
protected void BtnDelete_Click(object sender, EventArgs e)
{
//刪除用戶
try
{
ISqlMapper mapper = Mapper.Instance(); //得到ISqlMapper實例
mapper.Delete("SysuserMap.DeleteSysuser", 1);//調用Delete方法
Label1.Text = "刪除用戶成功";
}
catch (Exception ex)
{
Label1.Text = ex.Message;
}
}
(4)查詢
查詢操作使用的語句為select語句,XML數據映射文件配置信息為:
<select id="SelectSysuser" parameterClass="int" resultMap="SysuserResult">
SELECT USERID,PASSWORD,LOGINNAME,SEX,BIRTHDAY,IDCARD,OFFICEPHONE,FAMILYPHONE,MOBILEPHONE,EMAIL,ADDRESS,ZIPCODE,REMARK,STATUS,REGISTERDATE
FROM SYSUSER WHERE USERID = #Userid#
</select>
調用代碼為:
protected void BtnQuery_Click(object sender, EventArgs e)
{
//查詢用戶
try
{
ISqlMapper mapper = Mapper.Instance(); //得到ISqlMapper實例
int Userid = 1;
IList<iBatisTest.Domain.Sysuser> plist = mapper.QueryForList<iBatisTest.Domain.Sysuser>("SysuserMap.SelectSysuser", Userid);//調用QueryForList方法
if (plist != null && plist.Count > 0)
{
gvUserData.DataSource = plist;
gvUserData.DataBind();
}
Label1.Text = "查詢用戶成功";
}
catch (Exception ex)
{
Label1.Text = ex.Message;
}
}
調用ISqlMapper實例的QueryForList方法返回的是一個IList泛型接口,這里指定為iBatisTest.Domain.Sysuser對象。程序運行結果如圖3所示。
圖3:查詢結果
以上4種配置語句都可以用statement來代替,其配置信息和調用代碼完全一樣。但用statement定義所有操作,缺乏直觀性。建議在開發中根據實際情況來選用配置,既使得配置文件直觀,又可以借助xsd對節點聲明進行更有針對性的檢查,以避免配置上的失誤。
4 特殊配置
4.1 XML轉義字符
很多時候在SQL語句中會用到大於或者下於符號(即:><),這個時候就與XML規范相沖突,影響XML映射文件的合法性。通過加入CDATA節點來避免這種情況的發生,如:
<statement id="SelectPersonsByAge" parameterClass="int" resultClass="Person">
<![CDATA[
SELECT * FROM PERSON WHERE AGE > #value#
]]>
</statement>
4.2自動生成主鍵
目前很多數據庫都支持為新插入的記錄自動生成主鍵,Oracle也提供這種功能,通過序列加觸發器來解決。這當然很方便,但是當希望在插入一條記錄后立即獲得該記錄的主鍵值,問題就出現了,因為很可能為此不得不新寫一條語句去獲取該主鍵值。iBatis.Net提供了一種比較好的解決方式。通過在XML數據映射文件中的<selectKey>元素來獲取這些自動生成的主鍵值並將其保存在對象中。
如上面的"添加"操作配置信息修改為:
<insert id="InsertSysuser" parameterClass="Sysuser">
<selectKey resultClass="int32" property="Userid" type="pre">
SELECT DEAN.SEQ_SYSUSER_USERID.NEXTVAL AS Userid FROM DUAL
</selectKey>
INSERT INTO DEAN.SYSUSER (USERID,PASSWORD,LOGINNAME,SEX,BIRTHDAY,IDCARD,OFFICEPHONE,FAMILYPHONE,MOBILEPHONE,EMAIL,ADDRESS,ZIPCODE,REMARK,STATUS)
VALUES (#Userid#,#Password#,#Loginname#,#Sex#,#Birthday#,#Idcard#,#Officephone#,#Familyphone#,#Mobilephone#,#Email#,#Address#,#Zipcode#,#Remark#,#Status#)
</insert>
粗體部分為新加的selectKey節點。#Userid#參數將使用節點中從序列里選出的值進行填充。修改調用的代碼:
ISqlMapper mapper = Mapper.Instance(); //得到ISqlMapper實例
Int32 result = (Int32)mapper.Insert("SysuserMap.InsertSysuser", model);//調用Insert方法
Label1.Text = "添加用戶成功,result返回UserID為" + Convert.ToString(result) + ",Sysuser實例model的UserID為" + Convert.ToString(model.Userid);
運行程序,這樣result就得到想要獲取的鍵值,同時Sysuser的實例化對象model的UserID屬性也返回了該條新記錄的鍵值。
4.3 #與$的選擇
在XML數據映射文件中,參數用#號來表示。如前面提到的#Userid#,同時也可以使用$來標識。兩者有什么區別,以及如何選擇呢?
(1)#是把傳入的數據當作參數,如 order by #field# ,如果#field#傳入的值是字符型,傳入值為id, 則sql語句會生成order by "id",很明顯生成的sql語句提交給數據庫執行會報錯。#參數往往用在需傳入實際變量值的情況,如where id=#id#,變量#id#傳入10,則sql語句會生成where id=10。
(2)$采用拼接方式生成sql語句,即傳入的數據直接生成在sql語句里,如 order by $field$,如$field$傳入的是id,則sql語句會生成order by id。$參數方式一般用於傳入數據庫對象。例如傳入表名。不過要特別提醒,因為使用該參數是直接組成sql語句,所以需注意防止sql注入。
4.4 LIKE語句處理
在查詢處理過程中,模糊查詢經常會用到。iBatis如何處理這種模糊查詢呢?處理Like模糊查詢iBatis提供以下兩種方法:
(1)使用#參數: 配置如Where UserName Like #param#||'%', 采用參數傳遞的方式,如#param#傳入字符d,生成的sql語句為Where UserName Like 'd%',其他模糊情況同理可以相應配置出來。
(2)使用$參數:配置如Where UserName Like '$param$%' ,采用字符串替換,就是用參數的值替換param,如$param$傳入字符d,也可以生成相同的語句。
$方式會引起sql注入風險,實際的使用中,建議大家都使用第一種配置方式來處理LIKE模糊查詢。
5 結語
以上程序在Windows7(64位)+VS2012+Oracle 11g(64位)上測試通過。