一. 介紹
平常做企業(yè)級應用,需求變化是經(jīng)常的事,而很多基礎代碼重復也是很讓人頭疼的問(wèn)題。所以很多人會(huì )使用一些ORM框架來(lái)增強項目的可維護性、可擴展性。IBatis.Net就是一個(gè)比較易用的ORM框架,使用起來(lái)較為方便、靈活。IBatis.Net是從Ibatis的Java版本移植過(guò)來(lái)的.NET版本。iBATIS作為一種獨特的Data Mapper,使用SQL映射的方式將對象持久化至關(guān)系型數據庫。簡(jiǎn)單的理解就是它將我們在數據訪(fǎng)問(wèn)層實(shí)現的C#邏輯代碼,變?yōu)橥ㄟ^(guò)關(guān)系數據庫與對象的映射,將SQL邏輯放到外部的XML配置文件中,以方便以后的維護。
這個(gè)框架有兩個(gè)主要的組成部分,一個(gè)是SQL Maps,另一個(gè)是Data Access Objects。Sql Maps是這個(gè)框架的核心部分,通過(guò)使用Sql Maps你可以顯著(zhù)的節約數據庫操作的代碼量。SQL Maps使用一個(gè)簡(jiǎn)單的XML文件來(lái)實(shí)現從實(shí)體到SQL statements的映射。使用DAO,封裝了對數據的訪(fǎng)問(wèn),你可以動(dòng)態(tài)配置你的應用程序來(lái)訪(fǎng)問(wèn)不同的實(shí)體存儲機制。隱藏持久性層實(shí)現的細節,Data Access Objects允許你通過(guò)一個(gè)簡(jiǎn)單接口的來(lái)操作數據。
二. 準備工作
下面先讓我們來(lái)動(dòng)手實(shí)踐一下,看看如何使用和調用IbatisNet。
1. 下載IbatisNet軟件包:
http://ibatis.apache.org/dotnetdownloads.cgiiBATIS.NET Downloads
DataMapper 1.6.2 [
MD5] [
PGP] (BETA)
DataAccess 1.9.2 [
MD5] [
PGP] (BETA)
Source Revision 709676 [
MD5] [
PGP]
官方網(wǎng)站還提供了一個(gè)示例項目:
NPetShop Example Application2. 建立測試數據庫,并在數據庫中創(chuàng )建一個(gè)Person 表:
字段
類(lèi)型
大小
PER_ID
int
4
PER_FIRST_NAME
nvarchar
40
PER_LAST_NAME
nvarchar
40
PER_BIRTH_DATE
DateTime
8
PER_WEIGHT_KG
float
8
PER_HEIGHT_M
float
8
三. 開(kāi)發(fā)步驟
1. 邏輯結構
2. 建立項目
打開(kāi)VS.NET,新建一個(gè)“ASP.NET Web應用程序”項目。
3. 添加引用
在項目中添加下面dll的引用:
IBatisNet.Common.dll
IBatisNet.DataMapper.dll
IBatisNet.DataAccess.dll
4. 在項目中,添加配置文件 SqlMap.config
該文件為IBatis.net默認的配置文件,不能缺少,當然可以不必是Sql.config,但是如果改為其他的名字的話(huà)需要在前臺代碼中說(shuō)明,請參考下面的內容providers.config:該文件必須存在,并且不能改變它的文件名,該文件描述了如何連接數據庫,無(wú)須配置
結合上面示例中的IbatisNet配置文件,下面對配置文件中各節點(diǎn)的說(shuō)明:
<?xml version="1.0" encoding="utf-8"?>
<sqlMapConfig xmlns="http://ibatis.apache.org/dataMapper"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
<!-- 常量屬性,通過(guò)單獨文件properties.config加載-->
<properties resource="properties.config"/>
<!-- 常量屬性,通過(guò)程序集資源中 加載
<properties embedded="database.config, IBatisNetDemo"/>-->
<settings>
<setting useStatementNamespaces="${useStatementNamespaces}"/>
<setting cacheModelsEnabled="true"/>
<setting validateSqlMap="false"/>
</settings>
<!-- 數據驅動(dòng)提供類(lèi)配置文件的路徑和文件名 -->
<providers resource="providers.config"/>
<!-- 設置數據庫連接信息 -->
<database>
<provider name="${provider}"/>
<dataSource name="IBatisNetTest" connectionString="${connectionString}"/>
</database>
<!-- 指定項目?jì)扔成涞奈募奈恢?->
<sqlMaps>
<!-- 從程序集中
<sqlMap embedded="${root}Person.xml,${assembly}" />-->
<!-- 從文件中-->
<sqlMap resource="@Maps/Person.xml" />
</sqlMaps>
</sqlMapConfig>
詳細說(shuō)明:
(1) properties節點(diǎn)
可以根據需要配置一些常量屬性。如果這些屬性有很多的話(huà)可以單獨寫(xiě)一個(gè)文件里面,再通過(guò)resource(或url, embedded分別是引用url和編譯在程序中的資源文件)屬性引用進(jìn)來(lái)。
properties 節點(diǎn)參數
參數
描述
resource
指定the properties文件從application的根目錄進(jìn)行加載
resource="properties.config"
url
指定the properties文件從文件的絕對路徑進(jìn)行加載
url="c:\Web\MyApp\Resources\properties.config"
或者
url="file://c:\Web\MyApp\Resources\properties.config"
embedded
指定文件可以作為程序集的資源文件進(jìn)行加載'
embedded=" database.config, IBatisNetDemo”
上面例子中properties.config文件的配置如下:
<?xml version="1.0" encoding="utf-8" ?>
<settings>
<!—應用程序和配置屬性設置-->
<add key="provider" value="sqlServer2.0" />
<add key="connectionString" value="server=127.0.0.1;database=TVSystem;uid=sa;pwd=1" />
<add key="root" value="IBatisNetTest.Maps." />
<add key="assembly" value="IBatisNetTest" />
<add key="userid" value="sa" />
<add key="password" value="" />
<add key="database" value="Northwind" />
<add key="datasource" value="localhost" />
<add key="selectKey" value="select @@IDENTITY as value" />
<add key="directory" value="Maps" />
<add key="useStatementNamespaces" value="false" />
</settings>
下面解釋一下這個(gè)文件的節點(diǎn)參數
properties.config節點(diǎn)參數
參數
描述
key
定義key (variable) 名字
key="username"
value
定義DataMapper 中使用的 key的值
value="mydbuser"
(2) setting節點(diǎn)
Setting節點(diǎn)參數
參數
描述
cacheModelsEnabled
是否啟用sqlMap上的緩存機制
Example: cacheModelsEnabled=”true”
Default: true (enabled)
useStatementNamespaces
是否使用Satement命名空間,這里的命名空間指的是映射文件中sqlMap節點(diǎn)的namespace屬性,如上例中針對Person表的映射文件sqlMap節點(diǎn)
<sqlMap namespace="Person" xmlns="http://ibatis.apache.org/mapping"
xmlns:xsi=
http://www.w3.org/2001/XMLSchema-instance> 這里,指定了此sqlMap節點(diǎn)下定義的操作均叢屬于“Person”命名空間在useStatementNamespaces=”true”的情況下,Statement調用需追加命名空間,如:sqlMap.Update(“Person.UpdatePerson”,person);否則直接通過(guò)Statement名稱(chēng)調用即可,如sqlMap.Update(“UpdatePerson”,person);但請注意此時(shí)需要保證所有映射文件中,statement定義無(wú)重名
Example: useStatementNamespaces=”false”
Default: false (disabled)
validateSqlMap
是配置要不要啟示SqlMapConfig.xsd schema驗證映射文件.
Example: validateSqlMap=”false”
Default: false (disabled)
(3) provider節點(diǎn)
配置數據驅動(dòng)提供類(lèi)配置文件的路徑和文件名,通過(guò)resource(或url, embedded分別是引用url和編譯在程序中的資源文件)屬性引用進(jìn)來(lái),參數的含義同properties。
ADO.NET是通過(guò)數據訪(fǎng)問(wèn)提供程序(Provider)訪(fǎng)問(wèn)數據庫。IBatisNet使用的是插件式結構來(lái)使用這些數據庫提供程序,每一個(gè)Provider對應于providers.config文件中定義的一個(gè)provider項。1.3版本的Provider.config文件中定義了已經(jīng)實(shí)現的16個(gè)provider,通過(guò)設置這個(gè)文件中的幾個(gè)參數來(lái)決定使用哪個(gè)數據庫提供程序。
· sqlServer1.0 - Microsoft SQL Server 7.0/2000 provider available with .NET Framework 1.0
· sqlServer1.1 -Microsoft SQL Server 7.0/2000 provider available with .NET Framework 1.1
· sqlServer2.0 - Microsoft SQL server 7.0/2000/2005 provider available with .NET Framework 2.0
· OleDb1.1 - OleDb provider available with .NET Framework 1.1
· Odbc1.1 - Odbc provider available with .NET Framework 1.1
· oracle9.2 - Oracle provider V9.2.0.401
· oracle10.1 - Oracle provider V10.1.0.301
· oracleClient1.0 - MS Oracle provider V1.0.5 available with .NET Framework 1.1
· ByteFx - ByteFx MySQL provider V0.7.6.15073
· MySql - MySQL provider V1.0.4.20163
· SQLite3 - SQLite.NET provider V0.21.1869.3794
· Firebird1.7 - Firebird SQL .NET provider V1.7.0.33200
· PostgreSql0.7 - Npgsql provider V0.7.0.0
· PostgreSql0.7.1 - Npgsql provider V0.7.1.0
· iDb2.10 - IBM DB2 iSeries provider V10.0.0.0
· Informix -- informix NET Provider, 2.81.0.0
提供程序要求安裝相關(guān)類(lèi)庫,每一個(gè)provider 元素都有"enabled" 屬性來(lái)控制是否啟用這個(gè)providers. 一個(gè)provider 可以通過(guò) “default“屬性標識為默認的提供程序。
(4) database節點(diǎn)
數據庫的信息,包括使用哪些數據庫驅動(dòng)和數據連接字符串的配置。
Database節點(diǎn)參數
參數
描述
provider
數據庫訪(fǎng)問(wèn)所使用的provider.config文件定義的provider
dataSource
特定的數據庫連接字符串
(5) typeHandler節點(diǎn)
定義數據庫類(lèi)型到dotnet數據類(lèi)型的處理,不同的數據庫都有一些特殊的數據庫字段類(lèi)型需要特殊處理,就可以通過(guò)這個(gè)功能實(shí)現。比如說(shuō)Blob字段在不同的數據庫中處理不一樣。大家可以去看看Ibatisnet源代碼就清楚這個(gè)功能的實(shí)現原理,對于我們的設計會(huì )有很大的啟發(fā)
(6) sqlMaps節點(diǎn)
sqlMap節點(diǎn)指定了映射文件的位置,配置中可以出現多個(gè)sqlMap節點(diǎn),以指定項目?jì)人乃杏成湮募?div style="height:15px;">
5. 創(chuàng )建實(shí)體類(lèi)
定義Person的實(shí)體類(lèi),該對象類(lèi)將與數據庫進(jìn)行映射。
[Serializable]
public class Person
{
private int id;
private string firstName;
private string lastName;
private DateTime? birthDate;
private double? weightInKilograms;
private double? heightInMeters;
public Person() { }
public int Id
{
get { return id; }
set { id = value; }
}
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
public DateTime? BirthDate
{
get { return birthDate; }
set { birthDate = value; }
}
public double? WeightInKilograms
{
get { return weightInKilograms; }
set { weightInKilograms = value; }
}
public double? HeightInMeters
{
get { return heightInMeters; }
set { heightInMeters = value; }
}
}
6. 添加Person的映射文件Person.xml
相對于Nhibernate等ORM實(shí)現來(lái)說(shuō),IBatisnet的映射配置更為直接,下面是配置文件內容:
<?xml version="1.0" encoding="utf-8" ?>
<sqlMap namespace="Person" xmlns="http://ibatis.apache.org/mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
<!--模塊配置-->
<alias>
<typeAlias alias="Person" type="IBatisNetLib.Person,IBatisNetLib" />
</alias>
<resultMaps>
<resultMap id="SelectAllResult" class="Person">
<result property="Id" column="PER_ID" />
<result property="FirstName" column="PER_FIRST_NAME" />
<result property="LastName" column="PER_LAST_NAME" />
<result property="BirthDate" column="PER_BIRTH_DATE" />
<result property="WeightInKilograms" column="PER_WEIGHT_KG" />
<result property="HeightInMeters" column="PER_HEIGHT_M" />
</resultMap>
</resultMaps>
<!--statement配置-->
<statements>
<select id="Exists" resultClass="int" parameterclass="int">
select count(1) from PERSON
where PER_ID = #value#
</select>
<insert id="InsertPerson" parameterclass="Person" >
<selectKey property="Id" type="post" resultClass="int">
${selectKey}
</selectKey>
insert into Person
( PER_FIRST_NAME,
PER_LAST_NAME,
PER_BIRTH_DATE,
PER_WEIGHT_KG,
PER_HEIGHT_M)values(#FirstName#,#LastName#,#BirthDate#, #WeightInKilograms#, #HeightInMeters#)
</insert>
<update id="UpdatePerson"
parameterclass="Person">
<![CDATA[ update Person set
PER_FIRST_NAME =#FirstName#,
PER_LAST_NAME =#LastName#,
PER_BIRTH_DATE =#BirthDate#,
PER_WEIGHT_KG=#WeightInKilograms#,
PER_HEIGHT_M=#HeightInMeters#
where
PER_ID = #Id# ]]>
</update>
<delete id="DeletePerson" parameterclass="Person">
delete from Person where PER_ID = #Id#
</delete>
<select id="SelectAllPerson" resultMap="SelectAllResult">
Select *
from PERSON
</select>
<select id="SelectByPersonId" resultMap="SelectAllResult" resultClass="Person" parameterClass="int">
select
PER_ID,
PER_FIRST_NAME,
PER_LAST_NAME,
PER_BIRTH_DATE,
PER_WEIGHT_KG,
PER_HEIGHT_M
from PERSON
where PER_ID = #value#
</select>
</statements>
</sqlMap>
可以看到,映射文件主要分為兩個(gè)部分:模塊配置 和 Statement配置。
模塊配置包括:
(1)Type Alias節點(diǎn)
定義了本映射文件中的別名,以避免過(guò)長(cháng)變量值的反復書(shū)寫(xiě),此例中通過(guò)typeAlias節點(diǎn)為類(lèi)“IBatisNetDemo.Domain.Person”定義了一個(gè)別名“Person”,這樣在本配置文件中的其他部分,需要引用“IBatisNetDemo.Domain.Person”類(lèi)時(shí),只需以其別名替代即可。
(2)cacheModel節點(diǎn)
定義了本映射文件中使用的Cache機制:
<cacheModel id="person-cache" implementation="MEMORY" >
<flushInterval hours="24"/>
<flushOnExecute statement="UpdateAccountViaInlineParameters"/>
<flushOnExecute statement="UpdateAccountViaParameterMap"/>
<property name="Type" value="Weak"/>
</cacheModel>
這里聲明了一個(gè)名為“person-cache”的cacheModel,之后可以在Statement聲明中對其進(jìn)行引用:
<select id="SelectAllPerson" resultMap="SelectAllResult" cacheModel=" person-cache">
select
PER_ID,
PER_FIRST_NAME,
PER_LAST_NAME,
PER_BIRTH_DATE,
PER_WEIGHT_KG,
PER_HEIGHT_M
from PERSON
</select>
這表明對通過(guò)id為SelAllPerson的“Select Statement”獲取的數據,使用CacheModel “person-cache”進(jìn)行緩存。之后如果程序再次用此Satement進(jìn)行數據查詢(xún)。即直接從緩存中讀取數據,而不需再去數據庫查詢(xún)。
CacheModel主要有幾個(gè)配置點(diǎn):
參數
描述
flushInterval
設定緩存有效期,如果超過(guò)此設定值,則將此CacheModel緩存清空
CacheSize
本Cachemodel中最大的數據對象數量
flushOnExecute
指定執行特定的Statement時(shí),將緩存清空。如UpdatePerson操作將更新數據庫中用戶(hù)信息,這將導致緩存中的數據對象與數據庫中的實(shí)際數據發(fā)生偏差,因此必須將緩存清空以避免臟數據的出現。
(3)resultMaps節點(diǎn)
resultMaps實(shí)現dotnet實(shí)體到數據庫字段的映射配置:
<resultMap id="SelectAllResult" class="Person">
<result property="Id" column="PER_ID" />
<result property="FirstName" column="PER_FIRST_NAME" />
<result property="LastName" column="PER_LAST_NAME" />
<result property="BirthDate" column="PER_BIRTH_DATE" />
<result property="WeightInKilograms" column="PER_WEIGHT_KG" />
<result property="HeightInMeters" column="PER_HEIGHT_M" />
</resultMap>
(4)Statement配置:
⑴ ID
指定了操作ID,之后我們可以在代碼中通過(guò)指定操作id 來(lái)執行此節點(diǎn)所定義的操作,如:
SqlMap.Update("UpdatePerson", person);
ID設定使得在一個(gè)配置文件中定義兩個(gè)同名節點(diǎn)成為可能(兩個(gè)update節點(diǎn),以不同id區分)
⑵ parameterClass
指定了操作所需的參數類(lèi)型,此例中update 操作以IBatisNetDemo.Domain.Person類(lèi)型的對象作為參數,目標是將提供的Person實(shí)例更新到數據庫。
parameterClass="Person"中,user為“IBatisNetDemo.Domain.Person”
類(lèi)的別名,別名可通過(guò)typeAlias節點(diǎn)指定,如示例配置文件中的:
<typeAlias alias="Person" type="IBatisNetDemo.Domain.Person,IBatisNetDemo" />
⑶ <![CDATA[……]]>
通過(guò)<![CDATA[……]]>節點(diǎn),可以避免SQL 中與XML 規范相沖突的字符對XML映射文件的合法性造成影響。
⑷ 執行更新操作的SQL,這里的SQL 即實(shí)際數據庫支持的SQL 語(yǔ)句,將由IBatisNet填入參數后交給數據庫執行。
⑸ SQL中所需的用戶(hù)名參數,“# FirstName #”在運行期會(huì )由傳入的Person對象的FirstName屬性填充。
⑹ SQL 中所需的用戶(hù)性別參數“# LastName #”,將在運行期由傳入的user 對象的LastName屬性填充。
⑺ SQL中所需的條件參數“#id#”,將在運行期由傳入的Person對象的Person屬性填充。
對于這個(gè)示例,IBatisNet在運行期會(huì )讀取id 為“UpdatePerson”的update節點(diǎn)的SQL定義,并調用指定的user對象的對應getter方法獲取屬性值,并用此屬性值,對SQL中的參數進(jìn)行填充后提交數據庫執行。
Statement配置包含了數個(gè)與Sql Statement相關(guān)的節點(diǎn),<statement>元素是一個(gè)通用的能夠包容任意類(lèi)型sql的元素。我們可以用更多細節的元素。
這些細節元素提供更好的錯誤檢查以及一些更多的功能。(例如,一個(gè)插入函數能夠返回數據庫自動(dòng)生成的key)。以下表格總結了聲明類(lèi)型元素以及他們的特性和屬性。
Statement Element
Attributes
Child Elements
Methods
<statement>
id
parameterClass
resultClass
parameterMap
resultMap
cacheModel
xmlResultName (Java only)
All dynamic elements
insert
update
delete
All query methods
<insert>
id
parameterClass
parameterMap
All dynamic elements
<selectKey>
<generate> (.NET only)
insert
update
delete
<update>
id
parameterClass
parameterMap
All dynamic elements
<generate> (.NET only)
insert
update
delete
<delete>
id
parameterClass
parameterMap
All dynamic elements
<generate> (.NET only)
insert
update
delete
<select>
id
parameterClass
resultClass
parameterMap
resultMap
cacheModel
All dynamic elements
<generate> (.NET only)
All query methods
<procedure>
id
parameterClass
resultClass
parameterMap
resultMap
xmlResultName (Java only)
All dynamic elements
insert
update
delete
All query methods
其中,statement最為通用,它可以代替其余的所有節點(diǎn)。除statement之外的節點(diǎn)對應于SQL中的同名操作(procedure對應存儲過(guò)程)。使用Statement定義所有操作,缺乏直觀(guān)性,建議在開(kāi)發(fā)中根據操作目的,各自選用對應的節點(diǎn)名加以說(shuō)明。一方面,使得配置文件更加直觀(guān),另一方面,也可以借助xsd對i節點(diǎn)聲明進(jìn)行更有針對性的檢查,以避免配置上的失誤。
<statement id=”statementName”
[parameterMap=”nameOfParameterMap”]
[parameterClass=”some.class.Name”]
[resultMap=”nameOfResultMap”]
[resultClass=”some.class.Name”]
[cacheModel=”nameOfCache”]
>
select * from PRODUCT where PRD_ID = [?|#propertyName#]
order by [$simpleDynamic$]
</statement>
其中“[ ]”包圍的部分為可能出現的配置項,各參數說(shuō)明見(jiàn)下表。具體的使用方法參見(jiàn)IBatisNet官方文檔。
參數
描述
parameterMap
參數映射,需結合parameterMap節點(diǎn)對映射關(guān)系加以定義,對于存儲過(guò)程之外的statement而言,建議使用parameterClass作為參數配置方式,一方面避免了參數映射配置工作,另一方面其性能表現更加出色
parameterClass
參數類(lèi)。指定了參數類(lèi)型的完整類(lèi)名(包括命名空間),可以通過(guò)別名避免每次書(shū)寫(xiě)冗長(cháng)的類(lèi)名
resultMap
結果映射,需結合resultMap節點(diǎn)對映射關(guān)系加以定義
resultClass
結果類(lèi)。指定了結果類(lèi)型的完整類(lèi)名(包括命名空間),可以通過(guò)別名避免每次書(shū)寫(xiě)冗長(cháng)的類(lèi)名
cacheModel
Statement對應的Cache模塊
一般而言,對于insert、update、delete、select語(yǔ)句,優(yōu)先采用parameterClass和resultClass.。paremeterMap使用較少,而ResultMap則大多用于存儲過(guò)程處理和查詢(xún)。存儲過(guò)程相對而言比較封閉(很多情況下需要調用現有的存儲過(guò)程),其參數名和返回的數據字段命名往往不符合dotnet編程的命名規范)。使用resultMap建立字段名同Dotnet對象的屬性之間的映射關(guān)系就非常有效。另一方面,由于通過(guò)ResultMap指定了字段名和字段類(lèi)型,ibatisnet無(wú)需再通過(guò)ado.net來(lái)動(dòng)態(tài)獲取字段信息,在一定程度上也提升了性能