IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
OpenDoc Series’
iBATIS 2.0 開發(fā)指南
V1.0
作者:夏昕 xiaxin(at)gmail.com
So many open source projects. Why not Open your Documents? J
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
文檔說明
參與人員:
作者 聯(lián)絡(luò)
夏昕 xiaxin(at)gmail.com
(at) 為email @ 符號(hào)
發(fā)布記錄
版本 日期 作者 說明
0.0 2004.8.1 夏昕 第一版
1.0 2004.9.1 夏昕 補(bǔ)充ibatis in Spring部分
OpenDoc版權(quán)說明
本文檔版權(quán)歸原作者所有。
在免費(fèi)、且無任何附加條件的前提下,可在網(wǎng)絡(luò)媒體中自由傳播。
如需部分或者全文引用,請(qǐng)事先征求作者意見。
如果本文對(duì)您有些許幫助,表達(dá)謝意的最好方式,是將您發(fā)現(xiàn)的問題和文檔改進(jìn)意見及時(shí)反饋給
作者。當(dāng)然,倘若有時(shí)間和能力,能為技術(shù)群體無償貢獻(xiàn)自己的所學(xué)為最好的回饋。
另外,筆者近來試圖就日本、印度的軟件開發(fā)模式進(jìn)行一些調(diào)研。如果諸位可以贈(zèng)閱日本、印度
軟件研發(fā)過程中的需求、設(shè)計(jì)文檔以供研究,感激不盡!
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
ibatis 開發(fā)指南
ibatis Quick Start............................................................................................5
準(zhǔn)備工作..........................................................................................................5
構(gòu)建ibatis基礎(chǔ)代碼....................................................................................5
ibatis配置........................................................................................................... 11
ibatis基礎(chǔ)語(yǔ)義......................................................................................................16
XmlSqlMapClientBuilder...................................................................16
SqlMapClient ...........................................................................................16
SqlMapClient基本操作示例..........................................................16
OR映射...........................................................................................................19
ibatis高級(jí)特性......................................................................................................26
數(shù)據(jù)關(guān)聯(lián)........................................................................................................26
一對(duì)多關(guān)聯(lián)............................................................................................26
一對(duì)一關(guān)聯(lián)............................................................................................28
延遲加載........................................................................................................30
動(dòng)態(tài)映射........................................................................................................31
事務(wù)管理........................................................................................................35
基于JDBC的事務(wù)管理機(jī)制................................................................35
基于JTA的事務(wù)管理機(jī)制...................................................................36
外部事務(wù)管理.........................................................................................38
Cache..............................................................................................................39
MEMORY類型Cache與WeakReference ........................................40
LRU型Cache .......................................................................................42
FIFO型Cache ......................................................................................43
OSCache.................................................................................................43
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
ibatis 開發(fā)指南
相對(duì)Hibernate和Apache OJB 等“一站式”ORM解決方案而言,ibatis 是一種“半
自動(dòng)化”的ORM實(shí)現(xiàn)。
所謂“半自動(dòng)”,可能理解上有點(diǎn)生澀??v觀目前主流的ORM,無論Hibernate 還是
Apache OJB,都對(duì)數(shù)據(jù)庫(kù)結(jié)構(gòu)提供了較為完整的封裝,提供了從POJO 到數(shù)據(jù)庫(kù)表的全
套映射機(jī)制。程序員往往只需定義好了POJO 到數(shù)據(jù)庫(kù)表的映射關(guān)系,即可通過Hibernate
或者OJB 提供的方法完成持久層操作。程序員甚至不需要對(duì)SQL 的熟練掌握,
Hibernate/OJB 會(huì)根據(jù)制定的存儲(chǔ)邏輯,自動(dòng)生成對(duì)應(yīng)的SQL 并調(diào)用JDBC 接口加以執(zhí)
行。
大多數(shù)情況下(特別是對(duì)新項(xiàng)目,新系統(tǒng)的開發(fā)而言),這樣的機(jī)制無往不利,大有一
統(tǒng)天下的勢(shì)頭。但是,在一些特定的環(huán)境下,這種一站式的解決方案卻未必靈光。
在筆者的系統(tǒng)咨詢工作過程中,常常遇到以下情況:
1. 系統(tǒng)的部分或全部數(shù)據(jù)來自現(xiàn)有數(shù)據(jù)庫(kù),處于安全考慮,只對(duì)開發(fā)團(tuán)隊(duì)提供幾
條Select SQL(或存儲(chǔ)過程)以獲取所需數(shù)據(jù),具體的表結(jié)構(gòu)不予公開。
2. 開發(fā)規(guī)范中要求,所有牽涉到業(yè)務(wù)邏輯部分的數(shù)據(jù)庫(kù)操作,必須在數(shù)據(jù)庫(kù)層由
存儲(chǔ)過程實(shí)現(xiàn)(就筆者工作所面向的金融行業(yè)而言,工商銀行、中國(guó)銀行、交
通銀行,都在開發(fā)規(guī)范中嚴(yán)格指定)
3. 系統(tǒng)數(shù)據(jù)處理量巨大,性能要求極為苛刻,這往往意味著我們必須通過經(jīng)過高
度優(yōu)化的SQL語(yǔ)句(或存儲(chǔ)過程)才能達(dá)到系統(tǒng)性能設(shè)計(jì)指標(biāo)。
面對(duì)這樣的需求,再次舉起Hibernate 大刀,卻發(fā)現(xiàn)刀鋒不再銳利,甚至無法使用,
奈何?恍惚之際,只好再摸出JDBC 準(zhǔn)備拼死一搏……,說得未免有些凄涼,直接使用JDBC
進(jìn)行數(shù)據(jù)庫(kù)操作實(shí)際上也是不錯(cuò)的選擇,只是拖沓的數(shù)據(jù)庫(kù)訪問代碼,乏味的字段讀取操作
令人厭煩。
“半自動(dòng)化”的ibatis,卻剛好解決了這個(gè)問題。
這里的“半自動(dòng)化”,是相對(duì)Hibernate等提供了全面的數(shù)據(jù)庫(kù)封裝機(jī)制的“全自動(dòng)化”
ORM 實(shí)現(xiàn)而言,“全自動(dòng)”ORM 實(shí)現(xiàn)了POJO 和數(shù)據(jù)庫(kù)表之間的映射,以及SQL 的自動(dòng)
生成和執(zhí)行。而ibatis 的著力點(diǎn),則在于POJO 與SQL之間的映射關(guān)系。也就是說,ibatis
并不會(huì)為程序員在運(yùn)行期自動(dòng)生成SQL 執(zhí)行。具體的SQL 需要程序員編寫,然后通過映
射配置文件,將SQL所需的參數(shù),以及返回的結(jié)果字段映射到指定POJO。
使用ibatis 提供的ORM機(jī)制,對(duì)業(yè)務(wù)邏輯實(shí)現(xiàn)人員而言,面對(duì)的是純粹的Java對(duì)象,
這一層與通過Hibernate 實(shí)現(xiàn)ORM 而言基本一致,而對(duì)于具體的數(shù)據(jù)操作,Hibernate
會(huì)自動(dòng)生成SQL 語(yǔ)句,而ibatis 則要求開發(fā)者編寫具體的SQL 語(yǔ)句。相對(duì)Hibernate等
“全自動(dòng)”ORM機(jī)制而言,ibatis 以SQL開發(fā)的工作量和數(shù)據(jù)庫(kù)移植性上的讓步,為系統(tǒng)
設(shè)計(jì)提供了更大的自由空間。作為“全自動(dòng)”ORM 實(shí)現(xiàn)的一種有益補(bǔ)充,ibatis 的出現(xiàn)顯
得別具意義。
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
ibatis Quick Start
準(zhǔn)備工作
1. 下載ibatis軟件包(
http://www.ibatis.com)。
2. 創(chuàng)建測(cè)試數(shù)據(jù)庫(kù),并在數(shù)據(jù)庫(kù)中創(chuàng)建一個(gè)t_user 表,其中包含三個(gè)字段:
Ø id(int)
Ø name(varchar)
Ø sex(int)。
3. 為了在開發(fā)過程更加直觀,我們需要將ibatis日志打開以便觀察ibatis運(yùn)作的細(xì)節(jié)。
ibatis 采用Apache common_logging,并結(jié)合Apache log4j 作為日志輸出組件。在
CLASSPATH 中新建log4j.properties配置文件,內(nèi)容如下:
log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%c{1} - %m%n
log4j.logger.java.sql.PreparedStatement=DEBUG
構(gòu)建ibatis基礎(chǔ)代碼
ibatis 基礎(chǔ)代碼包括:
1. ibatis 實(shí)例配置
一個(gè)典型的配置文件如下(具體配置項(xiàng)目的含義見后):
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMapConfig
PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"
"
/>
<transactionManager type="JDBC">
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
<dataSource type="SIMPLE">
<property name="JDBC.Driver"
value="com.p6spy.engine.spy.P6SpyDriver"/>
<property name="JDBC.ConnectionURL"
value="jdbc:mysql://localhost/sample"/>
<property name="JDBC.Username" value="user"/>
<property name="JDBC.Password" value="mypass"/>
<property name="Pool.MaximumActiveConnections"
value="10"/>
<property name="Pool.MaximumIdleConnections" value="5"/>
<property name="Pool.MaximumCheckoutTime"
value="120000"/>
<property name="Pool.TimeToWait" value="500"/>
<property name="Pool.PingQuery" value="select 1 from
ACCOUNT"/>
<property name="Pool.PingEnabled" value="false"/>
<property name="Pool.PingConnectionsOlderThan"
value="1"/>
<property name="Pool.PingConnectionsNotUsedFor"
value="1"/>
</dataSource>
</transactionManager>
<sqlMap resource="com/ibatis/sample/User.xml"/>
</sqlMapConfig>
2. POJO(Plain Ordinary Java Object)
下面是我們用作示例的一個(gè)POJO:
public class User implements Serializable {
private Integer id;
private String name;
private Integer sex;
private Set addresses = new HashSet();
/** default constructor */
public User() {
}
public Integer getId() {
return this.id;
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Integer getSex() {
return this.sex;
}
public void setSex(Integer sex) {
this.sex = sex;
}
}
3. 映射文件
與Hibernate 不同。因?yàn)樾枰斯ぞ帉慡QL 代碼,ibatis 的映射文件一般采
用手動(dòng)編寫(通過Copy/Paste,手工編寫映射文件也并沒想象中的麻煩)。
針對(duì)上面POJO 的映射代碼如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMap
PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN"
"<sqlMap namespace="User">
<typeAlias alias="user" type="com.ibatis.sample.User"/>
<select id="getUser"
parameterClass="java.lang.String"
resultClass="user">
<![CDATA[
select
name,
sex
from t_user
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
where name = #name#
]]>
</select>
<update id="updateUser"
parameterClass="user">
<![CDATA[
UPDATE t_user
SET
name=#name#,
sex=#sex#
WHERE id = #id#
]]>
</update>
<insert id="insertUser"
parameterClass="user"
>
INSERT INTO t_user (
name,
sex)
VALUES (
#name#,
#sex#
)
</insert>
<delete id="deleteUser"
parameterClass="java.lang.String">
delete from t_user
where id = #value#
</delete>
</sqlMap>
從上面的映射文件可以看出,通過<insert>、<delete>、<update>、
<select>四個(gè)節(jié)點(diǎn),我們分別定義了針對(duì)TUser 對(duì)象的增刪改查操作。在這
四個(gè)節(jié)點(diǎn)中,我們指定了對(duì)應(yīng)的SQL 語(yǔ)句,以u(píng)pdate節(jié)點(diǎn)為例:
……
<update id="updateUser" ⑴
parameterClass="user"> ⑵
<![CDATA[ ⑶
UPDATE t_user ⑷
SET (
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
name=#name#, ⑸
sex=#sex# ⑹
)
WHERE id = #id# ⑺
]]>
</update>
……
⑴ ID
指定了操作ID,之后我們可以在代碼中通過指定操作id 來執(zhí)行此節(jié)點(diǎn)所定
義的操作,如:
sqlMap.update("updateUser",user);
ID設(shè)定使得在一個(gè)配置文件中定義兩個(gè)同名節(jié)點(diǎn)成為可能(兩個(gè)update節(jié)
點(diǎn),以不同id區(qū)分)
⑵ parameterClass
指定了操作所需的參數(shù)類型, 此例中update 操作以
com.ibatis.sample.User 類型的對(duì)象作為參數(shù),目標(biāo)是將提供的User
實(shí)例更新到數(shù)據(jù)庫(kù)。
parameterClass="user"中,user為“com.ibatis.sample.User”
類的別名,別名可通過typeAlias節(jié)點(diǎn)指定,如示例配置文件中的:
<typeAlias alias="user" type="com.ibatis.sample.User"/>
⑶ <![CDATA[……]]>
通過<![CDATA[……]]>節(jié)點(diǎn),可以避免SQL 中與XML 規(guī)范相沖突的字符對(duì)
XML映射文件的合法性造成影響。
⑷ 執(zhí)行更新操作的SQL,這里的SQL 即實(shí)際數(shù)據(jù)庫(kù)支持的SQL 語(yǔ)句,將由
ibatis填入?yún)?shù)后交給數(shù)據(jù)庫(kù)執(zhí)行。
⑸ SQL中所需的用戶名參數(shù),“#name#”在運(yùn)行期會(huì)由傳入的user對(duì)象的name
屬性填充。
⑹ SQL 中所需的用戶性別參數(shù)“#sex#”,將在運(yùn)行期由傳入的user 對(duì)象的
sex屬性填充。
⑺ SQL中所需的條件參數(shù)“#id#”,將在運(yùn)行期由傳入的user對(duì)象的id屬性
填充。
對(duì)于這個(gè)示例,ibatis在運(yùn)行期會(huì)讀取id 為“updateUser”的update節(jié)點(diǎn)
的SQL定義,并調(diào)用指定的user對(duì)象的對(duì)應(yīng)getter方法獲取屬性值,并用此
屬性值,對(duì)SQL中的參數(shù)進(jìn)行填充后提交數(shù)據(jù)庫(kù)執(zhí)行。
此例對(duì)應(yīng)的應(yīng)用級(jí)代碼如下,其中演示了ibatis SQLMap的基本使用方法:
String resource ="com/ibatis/sample/SqlMapConfig.xml";
Reader reader;
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
reader = Resources.getResourceAsReader(resource);
XmlSqlMapClientBuilder xmlBuilder =
new XmlSqlMapClientBuilder();
SqlMapClient sqlMap = xmlBuilder.buildSqlMap(reader);
//sqlMap系統(tǒng)初始化完畢,開始執(zhí)行update操作
try{
sqlMap.startTransaction();
User user = new User();
user.setId(new Integer(1));
user.setName("Erica");
user.setSex(new Integer(1));
sqlMap.update("updateUser",user);
sqlMap.commitTransaction();
finally{
sqlMap.endTransaction();
}
其中,SqlMapClient是ibatis運(yùn)作的核心,所有操作均通過SqlMapClient
實(shí)例完成。
可以看出,對(duì)于應(yīng)用層而言,程序員面對(duì)的是傳統(tǒng)意義上的數(shù)據(jù)對(duì)象,而非JDBC
中煩雜的ResultSet,這使得上層邏輯開發(fā)人員的工作量大大減輕,同時(shí)代碼更
加清晰簡(jiǎn)潔。
數(shù)據(jù)庫(kù)操作在映射文件中加以定義,從而將數(shù)據(jù)存儲(chǔ)邏輯從上層邏輯代碼中獨(dú)立
出來。
而底層數(shù)據(jù)操作的SQL可配置化,使得我們可以控制最終的數(shù)據(jù)操作方式,通過
SQL的優(yōu)化獲得最佳的數(shù)據(jù)庫(kù)執(zhí)行效能,這在依賴SQL自動(dòng)生成的“全自動(dòng)”ORM
機(jī)制中是所難以實(shí)現(xiàn)的。
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
ibatis配置
結(jié)合上面示例中的ibatis配置文件。下面是對(duì)配置文件中各節(jié)點(diǎn)的說明:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMapConfig
PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"
"<sqlMapConfig>
<settings ⑴
cacheModelsEnabled="true"
enhancementEnabled="true"
lazyLoadingEnabled="true"
errorTracingEnabled="true"
maxRequests="32"
maxSessions="10"
maxTransactions="5"
useStatementNamespaces="false"
/>
<transactionManager type="JDBC"> ⑵
<dataSource type="SIMPLE"> ⑶
<property name="JDBC.Driver"
value="com.p6spy.engine.spy.P6SpyDriver"/>
<property name="JDBC.ConnectionURL"
value="jdbc:mysql://localhost/sample"/>
<property name="JDBC.Username" value="user"/>
<property name="JDBC.Password" value="mypass"/>
<property name="Pool.MaximumActiveConnections"
value="10"/>
<property name="Pool.MaximumIdleConnections" value="5"/>
<property name="Pool.MaximumCheckoutTime"
value="120000"/>
<property name="Pool.TimeToWait" value="500"/>
<property name="Pool.PingQuery" value="select 1 from
ACCOUNT"/>
<property name="Pool.PingEnabled" value="false"/>
<property name="Pool.PingConnectionsOlderThan"
value="1"/>
<property name="Pool.PingConnectionsNotUsedFor"
value="1"/>
</dataSource>
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
</transactionManager>
<sqlMap resource="com/ibatis/sample/User.xml"/> ⑷
<sqlMap resource="com/ibatis/sample/Address.xml"/>
</sqlMapConfig>
⑴ Settings 節(jié)點(diǎn)
參數(shù) 描述
cacheModelsEnabled 是否啟用SqlMapClient上的緩存機(jī)制。
建議設(shè)為"true"
enhancementEnabled 是否針對(duì)POJO啟用字節(jié)碼增強(qiáng)機(jī)制以提升
getter/setter的調(diào)用效能,避免使用Java
Reflect所帶來的性能開銷。
同時(shí),這也為L(zhǎng)azy Loading帶來了極大的性能
提升。
建議設(shè)為"true"
errorTracingEnabled 是否啟用錯(cuò)誤日志,在開發(fā)期間建議設(shè)為"true"
以方便調(diào)試
lazyLoadingEnabled 是否啟用延遲加載機(jī)制,建議設(shè)為"true"
maxRequests 最大并發(fā)請(qǐng)求數(shù)(Statement并發(fā)數(shù))
maxTransactions 最大并發(fā)事務(wù)數(shù)
maxSessions 最大Session 數(shù)。即當(dāng)前最大允許的并發(fā)
SqlMapClient數(shù)。
maxSessions設(shè)定必須介于
maxTransactions和maxRequests之間,即
maxTransactions<maxSessions=<
maxRequests
useStatementNamespaces 是否使用Statement命名空間。
這里的命名空間指的是映射文件中,sqlMap節(jié)點(diǎn)
的namespace屬性,如在上例中針對(duì)t_user
表的映射文件sqlMap節(jié)點(diǎn):
<sqlMap namespace="User">
這里,指定了此sqlMap節(jié)點(diǎn)下定義的操作均從
屬于"User"命名空間。
在useStatementNamespaces="true"的情
況下,Statement調(diào)用需追加命名空間,如:
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
sqlMap.update("User.updateUser",use
r);
否則直接通過Statement名稱調(diào)用即可,如:
sqlMap.update("updateUser",user);
但請(qǐng)注意此時(shí)需要保證所有映射文件中,
Statement定義無重名。
⑵ transactionManager節(jié)點(diǎn)
transactionManager 節(jié)點(diǎn)定義了ibatis 的事務(wù)管理器,目前提供了以下幾
種選擇:
Ø JDBC
通過傳統(tǒng)JDBC Connection.commit/rollback實(shí)現(xiàn)事務(wù)支持。
Ø JTA
使用容器提供的JTA服務(wù)實(shí)現(xiàn)全局事務(wù)管理。
Ø EXTERNAL
外部事務(wù)管理,如在EJB中使用ibatis,通過EJB的部署配置即可實(shí)現(xiàn)自
動(dòng)的事務(wù)管理機(jī)制。此時(shí)ibatis 將把所有事務(wù)委托給外部容器進(jìn)行管理。
此外,通過Spring 等輕量級(jí)容器實(shí)現(xiàn)事務(wù)的配置化管理也是一個(gè)不錯(cuò)的選
擇。關(guān)于結(jié)合容器實(shí)現(xiàn)事務(wù)管理,參見“高級(jí)特性”中的描述。
⑶ dataSource節(jié)點(diǎn)
dataSource從屬于transactionManager節(jié)點(diǎn),用于設(shè)定ibatis運(yùn)行期使
用的DataSource屬性。
type屬性: dataSource節(jié)點(diǎn)的type屬性指定了dataSource的實(shí)現(xiàn)類型。
可選項(xiàng)目:
Ø SIMPLE:
SIMPLE是ibatis內(nèi)置的dataSource實(shí)現(xiàn),其中實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的
數(shù)據(jù)庫(kù)連接池機(jī)制, 對(duì)應(yīng)ibatis 實(shí)現(xiàn)類為
com.ibatis.sqlmap.engine.datasource.SimpleDataSourceFactory。
Ø DBCP:
基于Apache DBCP 連接池組件實(shí)現(xiàn)的DataSource 封裝,當(dāng)無容器提
供DataSource 服務(wù)時(shí),建議使用該選項(xiàng),對(duì)應(yīng)ibatis 實(shí)現(xiàn)類為
com.ibatis.sqlmap.engine.datasource.DbcpDataSourceFactory。
Ø JNDI:
使用J2EE 容器提供的DataSource 實(shí)現(xiàn),DataSource 將通過指定
的JNDI Name 從容器中獲取。對(duì)應(yīng)ibatis 實(shí)現(xiàn)類為
com.ibatis.sqlmap.engine.datasource.JndiDataSourceFacto
ry。
dataSource的子節(jié)點(diǎn)說明(SIMPLE&DBCP):
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
參數(shù) 描述
JDBC.Driver JDBC 驅(qū)動(dòng)。
如:org.gjt.mm.mysql.Driver
JDBC.ConnectionURL 數(shù)據(jù)庫(kù)URL。
如:jdbc:mysql://localhost/sample
如果用的是SQLServer JDBC Driver,需要
在url后追加SelectMethod=Cursor以獲得
JDBC事務(wù)的多Statement支持。
JDBC.Username 數(shù)據(jù)庫(kù)用戶名
JDBC.Password 數(shù)據(jù)庫(kù)用戶密碼
Pool.MaximumActiveConn
ections
數(shù)據(jù)庫(kù)連接池可維持的最大容量。
Pool.MaximumIdleConnec
tions
數(shù)據(jù)庫(kù)連接池中允許的掛起(idle)連接數(shù)。
以上子節(jié)點(diǎn)適用于SIMPLE 和DBCP 模式,分別針對(duì)SIMPLE 和DBCP 模式的
DataSource私有配置節(jié)點(diǎn)如下:
SIMPLE:
參數(shù) 描述
Pool.MaximumCheckoutTi
me
數(shù)據(jù)庫(kù)聯(lián)接池中,連接被某個(gè)任務(wù)所允許占用的
最大時(shí)間,如果超過這個(gè)時(shí)間限定,連接將被強(qiáng)
制收回。(毫秒)
Pool.TimeToWait 當(dāng)線程試圖從連接池中獲取連接時(shí),連接池中無
可用連接可供使用,此時(shí)線程將進(jìn)入等待狀態(tài),
直到池中出現(xiàn)空閑連接。此參數(shù)設(shè)定了線程所允
許等待的最長(zhǎng)時(shí)間。(毫秒)
Pool.PingQuery 數(shù)據(jù)庫(kù)連接狀態(tài)檢測(cè)語(yǔ)句。
某些數(shù)據(jù)庫(kù)在連接在某段時(shí)間持續(xù)處于空閑狀態(tài)
時(shí)會(huì)將其斷開。而連接池管理器將通過此語(yǔ)句檢
測(cè)池中連接是否可用。
檢測(cè)語(yǔ)句應(yīng)該是一個(gè)最簡(jiǎn)化的無邏輯SQL。
如“select 1 from t_user”,如果執(zhí)行此語(yǔ)句
成功,連接池管理器將認(rèn)為此連接處于可用狀態(tài)。
Pool.PingEnabled 是否允許檢測(cè)連接狀態(tài)。
Pool.PingConnectionsOl
derThan
對(duì)持續(xù)連接時(shí)間超過設(shè)定值(毫秒)的連接進(jìn)行
檢測(cè)。
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
Pool.PingConnectionsNo
tUsedFor
對(duì)空閑超過設(shè)定值(毫秒)的連接進(jìn)行檢測(cè)。
DBCP:
參數(shù) 描述
Pool.MaximumWait 當(dāng)線程試圖從連接池中獲取連接時(shí),連接池中無
可用連接可供使用,此時(shí)線程將進(jìn)入等待狀態(tài),
直到池中出現(xiàn)空閑連接。此參數(shù)設(shè)定了線程所允
許等待的最長(zhǎng)時(shí)間。(毫秒)
Pool.ValidationQuery 數(shù)據(jù)庫(kù)連接狀態(tài)檢測(cè)語(yǔ)句。
某些數(shù)據(jù)庫(kù)在連接在某段時(shí)間持續(xù)處于空閑狀態(tài)
時(shí)會(huì)將其斷開。而連接池管理器將通過此語(yǔ)句檢
測(cè)池中連接是否可用。
檢測(cè)語(yǔ)句應(yīng)該是一個(gè)最簡(jiǎn)化的無邏輯SQL。
如“select 1 from t_user”,如果執(zhí)行此語(yǔ)句
成功,連接池管理器將認(rèn)為此連接處于可用狀態(tài)。
Pool.LogAbandoned 當(dāng)數(shù)據(jù)庫(kù)連接被廢棄時(shí),是否打印日志。
Pool.RemoveAbandonedTi
meout
數(shù)據(jù)庫(kù)連接被廢棄的最大超時(shí)時(shí)間
Pool.RemoveAbandoned 當(dāng)連接空閑時(shí)間超過
RemoveAbandonedTimeout時(shí),是否將其廢
棄。
JNDI由于大部分配置是在應(yīng)用服務(wù)器中進(jìn)行,因此ibatis中的配置相對(duì)簡(jiǎn)單,下面
是分別使用JDBC和JTA事務(wù)管理的JDNI配置:
使用JDBC事務(wù)管理的JNDI DataSource配置
<transactionManager type="JDBC" >
<dataSource type="JNDI">
<property name="DataSource"
value="java:comp/env/jdbc/myDataSource"/>
</dataSource>
</transactionManager>
<transactionManager type="JTA" >
<property name="UserTransaction"
value="java:/ctx/con/UserTransaction"/>
<dataSource type="JNDI">
<property name="DataSource"
value="java:comp/env/jdbc/myDataSource"/>
</dataSource>
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
</transactionManager>
⑷ sqlMap節(jié)點(diǎn)
sqlMap 節(jié)點(diǎn)指定了映射文件的位置,配置中可出現(xiàn)多個(gè)sqlMap 節(jié)點(diǎn),以指定
項(xiàng)目?jī)?nèi)所包含的所有映射文件。
ibatis基礎(chǔ)語(yǔ)義
XmlSqlMapClientBuilder
XmlSqlMapClientBuilder是ibatis 2.0之后版本新引入的組件,用以替代1.x
版本中的XmlSqlMapBuilder。其作用是根據(jù)配置文件創(chuàng)建SqlMapClient實(shí)例。
SqlMapClient
SqlMapClient是ibatis的核心組件,提供數(shù)據(jù)操作的基礎(chǔ)平臺(tái)。SqlMapClient
可通過XmlSqlMapClientBuilder創(chuàng)建:
String resource ="com/ibatis/sample/SqlMapConfig.xml";
Reader reader;
reader = Resources.getResourceAsReader(resource);
XmlSqlMapClientBuilder xmlBuilder =
new XmlSqlMapClientBuilder();
SqlMapClient sqlMap = xmlBuilder.buildSqlMap(reader);
"com/ibatis/sample/SqlMapConfig.xml"指明了配置文件在CLASSPATH
中的相對(duì)路徑。XmlSqlMapClientBuilder通過接受一個(gè)Reader類型的配置文
件句柄,根據(jù)配置參數(shù),創(chuàng)建SqlMapClient實(shí)例。
SqlMapClient提供了眾多數(shù)據(jù)操作方法,下面是一些常用方法的示例,具體說明
文檔請(qǐng)參見ibatis java doc,或者ibatis官方開發(fā)手冊(cè)。
SqlMapClient基本操作示例
以下示例摘自ibatis官方開發(fā)手冊(cè),筆者對(duì)其進(jìn)行了重新排版以獲得更好的閱讀效果。
例1: 數(shù)據(jù)寫入操作(insert, update, delete):
sqlMap.startTransaction();
Product product = new Product();
product.setId (1);
product.setDescription (“Shih Tzu”);
int rows = sqlMap.insert (“insertProduct”, product);
sqlMap.commitTransaction();
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
例2: 數(shù)據(jù)查詢 (select)
sqlMap.startTransaction();
Integer key = new Integer (1);
Product product = (Product)sqlMap.queryForObject
(“getProduct”, key);
sqlMap.commitTransaction();
例3: 在指定對(duì)象中存放查詢結(jié)果(select)
sqlMap.startTransaction();
Customer customer = new Customer();
sqlMap.queryForObject(“getCust”, parameterObject, customer);
sqlMap.queryForObject(“getAddr”, parameterObject, customer);
sqlMap.commitTransaction();
例4: 執(zhí)行批量查詢 (select)
sqlMap.startTransaction();
List list = sqlMap.queryForList (“getProductList”, null);
sqlMap.commitTransaction();
例5: 關(guān)于AutoCommit
//沒有預(yù)先執(zhí)行startTransaction時(shí),默認(rèn)為auto_commit模式
int rows = sqlMap.insert (“insertProduct”, product);
例6:查詢指定范圍內(nèi)的數(shù)據(jù)
sqlMap.startTransaction();
List list = sqlMap.queryForList (“getProductList”, null, 0, 40);
sqlMap.commitTransaction();
例7: 結(jié)合RowHandler進(jìn)行查詢(select)
public class MyRowHandler implements RowHandler {
public void handleRow (Object object, List list) throws
SQLException {
Product product = (Product) object;
product.setQuantity (10000);
sqlMap.update (“updateProduct”, product);
}
}
sqlMap.startTransaction();
RowHandler rowHandler = new MyRowHandler();
List list = sqlMap.queryForList (“getProductList”, null,
rowHandler);
sqlMap.commitTransaction();
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
例8: 分頁(yè)查詢 (select)
PaginatedList list =
sqlMap.queryForPaginatedList (“getProductList”, null, 10);
list.nextPage();
list.previousPage();
例9: 基于Map的批量查詢 (select)
sqlMap.startTransaction();
Map map = sqlMap.queryForMap (“getProductList”, null,
“productCode”);
sqlMap.commitTransaction();
Product p = (Product) map.get(“EST-93”);
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
OR 映射
相對(duì)Hibernate 等ORM 實(shí)現(xiàn)而言,ibatis的映射配置更為簡(jiǎn)潔直接,下面是一
個(gè)典型的配置文件。
<!DOCTYPE sqlMap
PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN"
"<sqlMap namespace="User">
<!--模塊配置-->
<typeAlias alias="user" type="com.ibatis.sample.User"/>
<cacheModel id="userCache" type="LRU">
<flushInterval hours="24"/>
<flushOnExecute statement=" updateUser"/>
<property name="size" value="1000" />
</cacheModel>
<!—Statement配置-->
<select id="getUser"
parameterClass="java.lang.String"
resultClass="user"
cacheModel="userCache"
>
<![CDATA[
select
name,
sex
from t_user
where name = #name#
]]>
</select>
<update id="updateUser"
parameterClass="user">
UPDATE t_user
SET
name=#name#,
sex=#sex#
WHERE id = #id#
</update>
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
</sqlMap>
可以看到,映射文件主要分為兩個(gè)部分:模塊配置和Statement配置。
模塊配置包括:
Ø typeAlias節(jié)點(diǎn):
定義了本映射文件中的別名,以避免過長(zhǎng)變量值的反復(fù)書寫,此例中通過
typeAlias節(jié)點(diǎn)為類"com.ibatis.sample.User"定義了一個(gè)別名"user",
這樣在本配置文件的其他部分,需要引用"com.ibatis.sample.User"類時(shí),
只需以其別名替代即可。
Ø cacheModel節(jié)點(diǎn)
定義了本映射文件中使用的Cache機(jī)制:
<cacheModel id="userCache" type="LRU">
<flushInterval hours="24"/>
<flushOnExecute statement="updateUser"/>
<property name="size" value="1000" />
</cacheModel>
這里申明了一個(gè)名為"userCache"的cacheModel,之后可以在
Statement申明中對(duì)其進(jìn)行引用:
<select id="getUser"
parameterClass="java.lang.String"
resultClass="user"
cacheModel="userCache"
>
這表明對(duì)通過id為"getUser"的Select statement獲取的數(shù)據(jù),使用
cacheModel "userCache"進(jìn)行緩存。之后如果程序再次用此Statement
進(jìn)行數(shù)據(jù)查詢,即直接從緩存中讀取查詢結(jié)果,而無需再去數(shù)據(jù)庫(kù)查詢。
cacheModel主要有下面幾個(gè)配置點(diǎn):
l flushInterval :
設(shè)定緩存有效期,如果超過此設(shè)定值,則將此CacheModel的緩存清空。
l size:
本CacheModel中最大容納的數(shù)據(jù)對(duì)象數(shù)量。
l flushOnExecute:
指定執(zhí)行特定Statement時(shí),將緩存清空。如updateUser操作將更
新數(shù)據(jù)庫(kù)中的用戶信息,這將導(dǎo)致緩存中的數(shù)據(jù)對(duì)象與數(shù)據(jù)庫(kù)中的實(shí)際
數(shù)據(jù)發(fā)生偏差,因此必須將緩存清空以避免臟數(shù)據(jù)的出現(xiàn)。
關(guān)于Cache的深入探討,請(qǐng)參見“高級(jí)特性”中的相關(guān)章節(jié)。
Statement配置:
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
Statement配置包含了數(shù)個(gè)與SQL Statement相關(guān)的節(jié)點(diǎn),分別為:
u statement
u insert
u delete
u update
u select
u procedure
其中,statement最為通用,它可以替代其余的所有節(jié)點(diǎn)。除statement之外
的節(jié)點(diǎn)各自對(duì)應(yīng)了SQL中的同名操作(procedure對(duì)應(yīng)存儲(chǔ)過程)。
使用statement 定義所有操作固然可以達(dá)成目標(biāo),但缺乏直觀性,建議在實(shí)際
開發(fā)中根據(jù)操作目的,各自選用對(duì)應(yīng)的節(jié)點(diǎn)名加以申明。一方面,使得配置文件
更加直觀,另一方面,也可借助DTD對(duì)節(jié)點(diǎn)申明進(jìn)行更有針對(duì)性的檢查,以避免
配置上的失誤。
各種類型的Statement 配置節(jié)點(diǎn)的參數(shù)類型基本一致,區(qū)別在于數(shù)量不同。如
insert、update、delete節(jié)點(diǎn)無需返回?cái)?shù)據(jù)類型定義(總是int)。
主要的配置項(xiàng)如下:
statement:
<statement id="statementName"
[parameterClass="some.class.Name"]
[resultClass="some.class.Name"]
[parameterMap="nameOfParameterMap"]
[resultMap="nameOfResultMap"]
[cacheModel="nameOfCache"]
> select * from t_user where sex = [?|#propertyName#]
order by [$simpleDynamic$]
</statement>
select:
<select id="statementName"
[parameterClass="some.class.Name"]
[resultClass="some.class.Name"]
[parameterMap="nameOfParameterMap"]
[resultMap="nameOfResultMap"]
[cacheModel="nameOfCache"]
> select * from t_user where sex = [?|#propertyName#]
order by [$simpleDynamic$]
</select>
Insert:
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
<insert id="statementName"
[parameterClass="some.class.Name"]
[parameterMap="nameOfParameterMap"]
> insert into t_user
(name,sex)
values
([?|#propertyName#],[?|#propertyName#])
</insert>
Update:
<update id="statementName"
[parameterClass="some.class.Name"]
[parameterMap="nameOfParameterMap"]
>
UPDATE t_user
SET
name=[?|#propertyName#],
sex=[?|#propertyName#]
WHERE id = [?|#propertyName#]
</update>
Delete:
<delete id="statementName"
[parameterClass="some.class.Name"]
[parameterMap="nameOfParameterMap"]
> delete from t_user
where id = [?|#propertyName#]
</delete>
其中以“[]”包圍的部分為可能出現(xiàn)的配置欄目。
參數(shù) 描述
parameterClass 參數(shù)類。指定了參數(shù)的完整類名(包括包路徑)。
可通過別名避免每次重復(fù)書寫冗長(zhǎng)的類名。
resultClass 結(jié)果類。指定結(jié)果類型的完整類名(包括包路徑)
可通過別名避免每次重復(fù)書寫冗長(zhǎng)的類名。
parameterMap 參數(shù)映射,需結(jié)合parameterMap節(jié)點(diǎn)對(duì)映射
關(guān)系加以定義。
對(duì)于存儲(chǔ)過程之外的statement而言,建議使用
parameterClass作為參數(shù)配置方式,一方面避
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
免了參數(shù)映射配置工作,另一方面其性能表現(xiàn)也
更加出色。
resultMap 結(jié)果映射,需結(jié)合resultMap節(jié)點(diǎn)對(duì)映射關(guān)系
加以定義。
cacheModel statement對(duì)應(yīng)的Cache模塊。
對(duì)于參數(shù)定義而言,盡量使用parameterClass,即直接將POJO 作為
statement 的調(diào)用參數(shù),這樣在SQL 中可以直接將POJO 的屬性作為參數(shù)加以
設(shè)定,如:
<update id="updateUser"
parameterClass="com.ibatis.sample.User">
UPDATE t_user
SET
name=#name#,
sex=#sex#
WHERE id = #id#
</update>
這里將com.ibatis.sample.User類設(shè)定為update statement的參數(shù),之后,
我們即可在SQL 中通過#propertyName#對(duì)POJO 的屬性進(jìn)行引用。如上例
中的:
SET name=#name#, sex=#sex# WHERE id=#id#
運(yùn)行期,ibatis 將通過調(diào)用User 對(duì)象的getName、getSex 和getId 方法獲得相
應(yīng)的參數(shù)值,并將其作為SQL 的參數(shù)。
如果parameterClass 中設(shè)定的是jdk 的中的簡(jiǎn)單對(duì)象類型,如String、
Integer,ibatis會(huì)直接將其作為SQL中的參數(shù)值。
我們也可以將包含了參數(shù)數(shù)據(jù)的Map對(duì)象傳遞給Statement,如:
<update id="updateUser"
parameterClass="java.util.Map">
UPDATE t_user
SET
name=#name#,
sex=#sex#
WHERE id = #id#
</update>
這里傳入的參數(shù)就是一個(gè)Map對(duì)象,ibatis將以key ”name”、”sex”、”id”從中
提取對(duì)應(yīng)的參數(shù)值。
同樣的原理,我們也可以在resultMap中設(shè)定返回類型為map。
<select id="getUser"
parameterClass="java.lang.String"
resultClass="java.util.Map">
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
<![CDATA[
select
id,
name,
sex
from t_user
where id = #id#
]]>
</select>
返回的結(jié)果將以各字段名為key保存在Map對(duì)象中返回。
在SQL中設(shè)定參數(shù)名時(shí),可以同時(shí)指定參數(shù)類型,如:
SET name=#name:VARCHAR#, sex=#sex:NUMERIC# WHERE
id=#id:NUMERIC#
對(duì)于返回結(jié)果而言,如果是select語(yǔ)句,建議也采用resultClass進(jìn)行定義,如:
<select id="getUser"
parameterClass="java.lang.String"
resultClass="user">
<![CDATA[
select
name,
sex
from t_user
where name = #name#
]]>
</select>
ibatis會(huì)自動(dòng)根據(jù)select語(yǔ)句中的字段名,調(diào)用對(duì)應(yīng)POJO 的set方法設(shè)定屬性
值,如上例中,ibatis會(huì)調(diào)用setName,setSex 方法將Select語(yǔ)句返回的數(shù)據(jù)賦
予相應(yīng)的POJO 實(shí)例。
有些時(shí)候,數(shù)據(jù)庫(kù)表中的字段名過于晦澀,而為了使得代碼更易于理解,我們
希望字段映射到POJO時(shí),采用比較易讀的屬性名, 此時(shí),我們可以通過Select
的as 字句對(duì)字段名進(jìn)行轉(zhuǎn)義,如(假設(shè)我們的書庫(kù)中對(duì)應(yīng)用戶名的字段為
xingming,對(duì)應(yīng)性別的字段為xingbie):
select
xingming as name,
xingbie as sex
from t_user
where id = #id#
ibatis 會(huì)根據(jù)轉(zhuǎn)義后的字段名進(jìn)行屬性映射(即調(diào)用POJO 的setName 方法而
不是setXingming方法)。
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
parameterMap和resultMap實(shí)現(xiàn)了POJO到數(shù)據(jù)庫(kù)字段的映射配置,下面是
一個(gè)例子:
<resultMap id="get_user_result" class="user">
<result property="name" column="xingming"
jdbcType="VARCHAR" javaType="java.lang.String"/>
<result property="sex" column="xingbie"
jdbcType="int" javaType="java.lang.Integer"/>
<result property="id" column="id"
jdbcType="int" javaType="java.lang.Integer"/>
</resultMap>
<parameterMap id="update_user_para" class="redemption" >
<parameter property="name"
jdbcType="VARCHAR"
javaType="java.lang.String"
nullValue=""
/>
<parameter property="sex"
jdbcType="int"
javaType="java.lang.Integer"
nullValue=""
/>
</parameterMap>
Parameter的nullValue指定了如果參數(shù)為空(null)時(shí)的默認(rèn)值。
之后我們即可在statement 申明中對(duì)其進(jìn)行引用,如:
<procedure id="getUserList"
resultMap="get_user_result"
>
{call sp_getUserList()}
</procedure>
<procedure id="doUserUpdate"
parameterMap="update_user_para"
>
{call sp_doUserUpdate(#id#,#name#,#sex#)}
</procedure>
一般而言,對(duì)于insert、update、delete、select語(yǔ)句,優(yōu)先采用parameterClass
和resultClass。
parameterMap 使用較少,而resultMap 則大多用于嵌套查詢以及存儲(chǔ)過程的
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
處理,之所以這樣,原因是由于存儲(chǔ)過程相對(duì)而言比較封閉(很多情況下需要調(diào)用現(xiàn)有
的存儲(chǔ)過程,其參數(shù)命名和返回的數(shù)據(jù)字段命名往往不符合Java編程中的命名習(xí)慣,
并且由于我們難以通過Select SQL的as子句進(jìn)行字段名轉(zhuǎn)義,無法使其自動(dòng)與POJO
中的屬性名相匹配)。此時(shí),使用resultMap建立字段名和POJO屬性名之間的映射
關(guān)系就顯得非常有效。另一方面,由于通過resultMap 指定了字段名和字段類型,
ibatis無需再通過JDBC ResultSetMetaData 來動(dòng)態(tài)獲取字段信息,在一定程度
上也提升了性能表現(xiàn)。
ibatis高級(jí)特性
數(shù)據(jù)關(guān)聯(lián)
至此,我們討論的都是針對(duì)獨(dú)立數(shù)據(jù)的操作。在實(shí)際開發(fā)中,我們常常遇到關(guān)聯(lián)數(shù)
據(jù)的情況,如User 對(duì)象擁有若干Address 對(duì)象,每個(gè)Address 對(duì)象描述了對(duì)應(yīng)User 的
一個(gè)聯(lián)系地址,這種情況下,我們應(yīng)該如何處理?
通過單獨(dú)的Statement操作固然可以實(shí)現(xiàn)(通過Statement 用于讀取用戶數(shù)據(jù),再手
工調(diào)用另外一個(gè)Statement 根據(jù)用戶ID 返回對(duì)應(yīng)的Address信息)。不過這樣未免失之
繁瑣。下面我們就看看在ibatis 中,如何對(duì)關(guān)聯(lián)數(shù)據(jù)進(jìn)行操作。
ibatis 中,提供了Statement 嵌套支持,通過Statement 嵌套,我們即可實(shí)現(xiàn)關(guān)聯(lián)數(shù)
據(jù)的操作。
一對(duì)多關(guān)聯(lián)
下面的例子中,我們首選讀取t_user 表中的所有用戶記錄,然后獲取每個(gè)用戶對(duì)應(yīng)
的所有地址信息。
配置文件如下:
<sqlMap namespace="User">
<typeAlias alias="user" type="com.ibatis.sample.User"/>
<typeAlias alias="address" type="com.ibatis.sample.Address"/>
<resultMap id="get-user-result" class="user">
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="sex" column="sex"/>
<result property="addresses" column="id"
select="User.getAddressByUserId"/>
</resultMap>
<select id="getUsers"
parameterClass="java.lang.String"
resultMap="get-user-result">
<![CDATA[
select
id,
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
name,
sex
from t_user
where id = #id#
]]>
</select>
<select id="getAddressByUserId"
parameterClass="int"
resultClass="address">
<![CDATA[
select
address,
zipcode
from t_address
where user_id = #userid#
]]>
</select>
</sqlMap>
對(duì)應(yīng)代碼:
String resource ="com/ibatis/sample/SqlMapConfig.xml";
Reader reader;
reader = Resources.getResourceAsReader(resource);
XmlSqlMapClientBuilder xmlBuilder = new XmlSqlMapClientBuilder();
sqlMap = xmlBuilder.buildSqlMap(reader);
//sqlMap系統(tǒng)初始化完畢
List userList = sqlMap.queryForList("User.getUsers", "");
for (int i = 0; i < userList.size(); i++) {
User user = (User)userList.get(i);
System.out.println("==>" + user.getName());
for (int k = 0; k < user.getAddresses().size(); k++) {
Address addr = (Address) user.getAddresses().get(k);
System.out.println(addr.getAddress());
}
}
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
這里通過在resultMap 中定義嵌套查詢getAddressByUserId,我們實(shí)現(xiàn)了關(guān)聯(lián)
數(shù)據(jù)的讀取。
實(shí)際上,這種方式類似于前面所說的通過兩條單獨(dú)的Statement 進(jìn)行關(guān)聯(lián)數(shù)據(jù)的讀
取,只是將關(guān)聯(lián)關(guān)系在配置中加以描述,由ibatis自動(dòng)完成關(guān)聯(lián)數(shù)據(jù)的讀取。
需要注意的是,這里有一個(gè)潛在的性能問題,也就是所謂“n+1”Select問題。
注意上面示例運(yùn)行過程中的日志輸出:
……
PreparedStatement - {pstm-100001} PreparedStatement: select id, name, sex from
t_user
……
PreparedStatement - {pstm-100004} PreparedStatement: select address, zipcode from
t_address where user_id = ?
……
PreparedStatement - {pstm-100007} PreparedStatement: select address,zipcode from
t_address where user_id = ?
第一條PreparedStatement 將t_user 表中的所有數(shù)據(jù)讀取出來(目前t_user 表中有兩
條測(cè)試數(shù)據(jù)),隨即,通過兩次Select 操作,從t_address 表中讀取兩個(gè)用戶所關(guān)聯(lián)的
Address記錄。
如果t_user 表中記錄較少,不會(huì)有明顯的影響,假設(shè)t_user 表中有十萬條記錄,那
么這樣的操作將需要100000+1 條Select語(yǔ)句反復(fù)執(zhí)行才能獲得結(jié)果,無疑,隨著記錄
的增長(zhǎng),這樣的開銷將無法承受。
之所以在這里提及這個(gè)問題,目的在于引起讀者的注意,在系統(tǒng)設(shè)計(jì)中根據(jù)具體情
況,采用一些規(guī)避手段(如使用存儲(chǔ)過程集中處理大批量關(guān)聯(lián)數(shù)據(jù)),從而避免因?yàn)檫@
個(gè)問題而引起產(chǎn)品品質(zhì)上的缺陷。
一對(duì)一關(guān)聯(lián)
一對(duì)一關(guān)聯(lián)是一對(duì)多關(guān)聯(lián)的一種特例。這種情況下,如果采用上面的示例將導(dǎo)致
1+1條SQL的執(zhí)行。
對(duì)于這種情況,我們可以采用一次Select兩張表的方式,避免這樣的性能開銷(假
設(shè)上面示例中,每個(gè)User 只有一個(gè)對(duì)應(yīng)的Address記錄):
<resultMap id="get-user-result" class="user">
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="sex" column="sex"/>
<result property="address" column="t_address.address"/>
<result property="zipCode" column="t_address.zipcode"/>
</resultMap>
<select id="getUsers"
parameterClass="java.lang.String"
resultMap="get-user-result">
<![CDATA[
select
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
*
from t_user,t_address
where t_user.id=t_address.user_id
]]>
</select>
與此同時(shí),應(yīng)該保證User 類中包含address和zipCode兩個(gè)String型屬性。
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
延遲加載
在運(yùn)行上面的例子時(shí),通過觀察期間的日志輸出順序我們可以發(fā)現(xiàn),在我們執(zhí)行
sqlMap.queryForList("User.getUsers", "")時(shí),實(shí)際上ibatis只向數(shù)據(jù)庫(kù)發(fā)送
了一條select id, name, sex from t_user SQL。而用于獲取Address記錄的SQL,只有在我
們真正訪問address對(duì)象時(shí),才開始執(zhí)行。
這也就是所謂的延遲加載(Lazy Loading)機(jī)制。即當(dāng)真正需要數(shù)據(jù)的時(shí)候,才加
載數(shù)據(jù)。延遲加載機(jī)制能為我們的系統(tǒng)性能帶來極大的提升。
試想,如果我們只需要獲取用戶名稱和性別數(shù)據(jù),在沒有延遲加載特性的情況下,
ibatis會(huì)一次將所有數(shù)據(jù)都從數(shù)據(jù)庫(kù)取回,包括用戶信息及其相關(guān)的地址數(shù)據(jù),而此時(shí),
關(guān)于地址數(shù)據(jù)的讀取操作沒有意義,也就是說,我們白白在地址數(shù)據(jù)的查詢讀取上浪費(fèi)
了大量的系統(tǒng)資源。延遲加載為我們妥善的處理了性能與編碼上的平衡(如果沒有延遲
加載,我們?yōu)榱吮苊鉄o謂的性能開銷,只能專門為此再增加一個(gè)不讀取地址信息的用戶
記錄檢索模塊,無疑增加了編碼上的工作量)。
回憶之前“ibatis配置”中的內(nèi)容:
<settings ⑴
……
enhancementEnabled="true"
lazyLoadingEnabled="true"
……
/>
Settings 節(jié)點(diǎn)有兩個(gè)與延遲加載相關(guān)的屬性lazyLoadingEnabled 和
enhancementEnabled,其中l(wèi)azyLoadingEnabled設(shè)定了系統(tǒng)是否使用延遲加載
機(jī)制,enhancementEnabled設(shè)定是否啟用字節(jié)碼強(qiáng)化機(jī)制(通過字節(jié)碼強(qiáng)化機(jī)制可
以為L(zhǎng)azy Loading帶來性能方面的改進(jìn)。
為了使用延遲加載所帶來的性能優(yōu)勢(shì),這兩項(xiàng)都建議設(shè)為"true"。
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
動(dòng)態(tài)映射
在復(fù)雜查詢過程中,我們常常需要根據(jù)用戶的選擇決定查詢條件,這里發(fā)生變化的
并不只是SQL 中的參數(shù),包括Select 語(yǔ)句中所包括的字段和限定條件,都可能發(fā)生變
化。典型情況,如在一個(gè)復(fù)雜的組合查詢頁(yè)面,我們必須根據(jù)用戶的選擇和輸入決定查
詢的條件組合。
一個(gè)典型的頁(yè)面如下:
對(duì)于這個(gè)組合查詢頁(yè)面,根據(jù)用戶選擇填寫的內(nèi)容,我們應(yīng)為其生成不同的查詢語(yǔ)
句。
如用戶沒有填寫任何信息即提交查詢請(qǐng)求,我們應(yīng)該返回所有記錄:
Select * from t_user;
如用戶只在頁(yè)面上填寫了姓名“Erica”,我們應(yīng)該生成類似:
Select * from t_user where name like ‘%Erica%’ ;
的SQL查詢語(yǔ)句。
如用戶只在頁(yè)面上填寫了地址“Beijing”,我們應(yīng)該生成類似:
Select * from t_user where address like ‘%Beijing%”;
的SQL。
而如果用戶同時(shí)填寫了姓名和地址(”Erica”&’Beijing’),則我們應(yīng)生成類似:
Select * from t_user where name like ‘%Erica%’ and address like ‘%Beijing%”
的SQL查詢語(yǔ)句。
對(duì)于ibatis 這樣需要預(yù)先指定SQL 語(yǔ)句的ORM 實(shí)現(xiàn)而言,傳統(tǒng)的做法無非通過
if-else 語(yǔ)句對(duì)輸入?yún)?shù)加以判定,然后針對(duì)用戶選擇調(diào)用不同的statement 定義。對(duì)于
上面這種簡(jiǎn)單的情況(兩種查詢條件的排列組合,共4 種情況)而言,statement 的重
復(fù)定義工作已經(jīng)讓人不厭其煩,而對(duì)于動(dòng)輒擁有七八個(gè)查詢條件,乃至十幾個(gè)查詢條件
的排列組合而言,瑣碎反復(fù)的statement定義實(shí)在讓人不堪承受。
考慮到這個(gè)問題,ibatis引入了動(dòng)態(tài)映射機(jī)制,即在statement定義中,根據(jù)不同的
查詢參數(shù),設(shè)定對(duì)應(yīng)的SQL語(yǔ)句。
還是以上面的示例為例:
<select id="getUsers"
parameterClass="user"
resultMap="get-user-result">
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
select
id,
name,
sex
from t_user
<dynamic prepend="WHERE">
<isNotEmpty prepend="AND" property="name">
(name like #name#)
</isNotEmpty>
<isNotEmpty prepend="AND" property="address">
(address like #address#)
</isNotEmpty>
</dynamic>
</select>
通過dynamic 節(jié)點(diǎn),我們定義了一個(gè)動(dòng)態(tài)的WHERE 子句。此WHERE 子句中將
可能包含兩個(gè)針對(duì)name 和address 字段的判斷條件。而這兩個(gè)字段是否加入檢索取決
于用戶所提供的查詢條件(字段是否為空[isNotEmpty])。
對(duì)于一個(gè)典型的Web程序而言,我們通過HttpServletRequest獲得表單中的字段名
并將其設(shè)入查詢參數(shù),如:
user.setName(request.getParameter("name"));
user.setAddress(request.getParameter("address"));
sqlMap.queryForList("User.getUsers", user);
在執(zhí)行queryForList("User.getUsers", user)時(shí),ibatis 即根據(jù)配置文
件中設(shè)定的SQL動(dòng)態(tài)生成規(guī)則,創(chuàng)建相應(yīng)的SQL語(yǔ)句。
上面的示例中,我們通過判定節(jié)點(diǎn)isNotEmpty,指定了關(guān)于name 和address 屬
性的動(dòng)態(tài)規(guī)則:
<isNotEmpty prepend="AND" property="name">
(name like #name#)
</isNotEmpty>
這個(gè)節(jié)點(diǎn)對(duì)應(yīng)的語(yǔ)義是,如果參數(shù)類的"name"屬性非空(isNotEmpty,即非空
字符串””),則在生成的SQL Where字句中包括判定條件(name like #name#),其
中#name#將以參數(shù)類的name屬性值填充。
Address屬性的判定生成與name屬性完全相同,這里就不再贅述。
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
這樣,我們通過在statement 定義中引入dynamic 節(jié)點(diǎn),很簡(jiǎn)單的實(shí)現(xiàn)了SQL 判
定子句的動(dòng)態(tài)生成,對(duì)于復(fù)雜的組合查詢而言,這將帶來極大的便利。
判定節(jié)點(diǎn)的定義可以非常靈活,我們甚至可以使用嵌套的判定節(jié)點(diǎn)來實(shí)現(xiàn)復(fù)雜的動(dòng)
態(tài)映射,如:
<isNotEmpty prepend="AND" property="name">
( name=#name#
<isNotEmpty prepend="AND" property="address">
address=#address#
</isNotEmpty>
)
</isNotEmpty>
這段定義規(guī)定,只有用戶提供了姓名信息時(shí),才能結(jié)合地址數(shù)據(jù)進(jìn)行查詢(如果只
提供地址數(shù)據(jù),而將姓名信息忽略,將依然被視為全檢索)。
Dynamic節(jié)點(diǎn)和判定節(jié)點(diǎn)中的prepend屬性,指明了本節(jié)點(diǎn)中定義的SQL子句在
主體SQL中出現(xiàn)時(shí)的前綴。
如:
<dynamic prepend="WHERE">
<isNotEmpty prepend="AND" property="name">
(name like #name#)
</isNotEmpty>
<isNotEmpty prepend="AND" property="address">
(address like #address#)
</isNotEmpty>
</dynamic>
假設(shè)"name"屬性的值為“Erica”, "address"屬性的值為“Beijing”,則會(huì)
生成類似下面的SQL子句(實(shí)際運(yùn)行期將生成帶占位符的PreparedStatement,之
后再為其填充數(shù)據(jù)):
WHERE (name like ‘Beijing’) AND (address like ‘Beijing’)
其中WHERE 之后的語(yǔ)句是在dynamic 節(jié)點(diǎn)中所定義,因此以dynamic 節(jié)點(diǎn)的
prepend設(shè)置("WHERE")作為前綴,而其中的”AND”,實(shí)際上是address屬性所對(duì)
應(yīng)的isNotEmpty節(jié)點(diǎn)的prepend設(shè)定,它引領(lǐng)了對(duì)應(yīng)節(jié)點(diǎn)中定義的SQL子句。至于
name屬性對(duì)應(yīng)的isNotEmpty節(jié)點(diǎn),由于ibatis會(huì)自動(dòng)判定是否需要追加prepend
前綴,這里(name like #name#)是WHERE 子句中的第一個(gè)條件子句,無需AND 前
綴,所以自動(dòng)省略。
判定節(jié)點(diǎn)并非僅限于isNotEmpty,ibatis中提供了豐富的判定定義功能。
判定節(jié)點(diǎn)分兩類:
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
Ø 一元判定
一元判定是針對(duì)屬性值本身的判定,如屬性是否為NULL,是否為空值等。
上面示例中isNotEmpty就是典型的一元判定。
一元判定節(jié)點(diǎn)有:
節(jié)點(diǎn)名 描述
<isPropertyAvailable> 參數(shù)類中是否提供了此屬性
<isNotPropertyAvailable> 與<isPropertyAvailable>相反
<isNull> 屬性值是否為NULL
<isNotNull> 與<isNull>相反
<isEmpty> 如果屬性為Collection或者String,其size是否<1,
如果非以上兩種類型,則通過
String.valueOf(屬性值)
獲得其String類型的值后,判斷其size是否<1
<isNotEmpty> 與<isEmpty>相反。
Ø 二元判定
二元判定有兩個(gè)判定參數(shù),一是屬性名,而是判定值,如
<isGreaterThan prepend="AND" property="age"
compareValue="18">
(age=#age#)
</isGreaterThan>
其中,property="age"指定了屬性名”age”,compareValue=”18”指明
了判定值為”18”。
上面判定節(jié)點(diǎn)isGreaterThan 對(duì)應(yīng)的語(yǔ)義是:如果age 屬性大于
18(compareValue),則在SQL中加入(age=#age#)條件。
二元判定節(jié)點(diǎn)有:
節(jié)點(diǎn)名 屬性值與compareValues的關(guān)系
<isEqual> 相等。
<isNotEqual> 不等。
<isGreaterThan> 大于
<isGreaterEqual> 大于等于
<isLessThan> 小于
<isLessEqual> 小于等于
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
事務(wù)管理
基于JDBC的事務(wù)管理機(jī)制
ibatis提供了自動(dòng)化的JDBC事務(wù)管理機(jī)制。
對(duì)于傳統(tǒng)JDBC Connection 而言,我們獲取Connection 實(shí)例之后,需要調(diào)用
Connection.setAutoCommit設(shè)定事務(wù)提交模式。
在AutoCommit為true的情況下,JDBC會(huì)對(duì)我們的操作進(jìn)行自動(dòng)提交,此時(shí),每
個(gè)JDBC操作都是一個(gè)獨(dú)立的任務(wù)。
為了實(shí)現(xiàn)整體事務(wù)的原子性,我們需要將AutoCommit 設(shè)為false,并結(jié)合
Connection.commit/rollback方法進(jìn)行事務(wù)的提交/回滾操作。
ibatis 的所謂“自動(dòng)化的事務(wù)提交機(jī)制”,即ibatis 會(huì)根據(jù)當(dāng)前的調(diào)用環(huán)境,自動(dòng)
判斷操作是否需要自動(dòng)提交。
如果代碼沒有顯式的調(diào)用SqlMapClient.startTransaction()方法,則ibatis
會(huì)將當(dāng)前的數(shù)據(jù)庫(kù)操作視為自動(dòng)提交模式(AutoCommit=true),如:
sqlMap = xmlBuilder.buildSqlMap(reader);
User user = new User();
user.setId(new Integer(1));
user.setName("Erica");
user.setSex(new Integer(0));
sqlMap.update("User.updateUser", user);
User user2 = new User();
user2.setId(new Integer(2));
user2.setName("Kevin");
user2.setSex(new Integer(1));
sqlMap.update("User.updateUser", user2);
在執(zhí)行sqlMap.update的時(shí)候,ibatis會(huì)自動(dòng)判定當(dāng)前的運(yùn)行環(huán)境,這里update操作
并沒有相對(duì)應(yīng)的事務(wù)范圍(startTransaction和endTransaction代碼塊),于是
ibatis 將其作為一個(gè)單獨(dú)的事務(wù),并自動(dòng)提交。對(duì)于上面的代碼,update 執(zhí)行了兩次,
與其相對(duì)應(yīng),事務(wù)也提交了兩次(即每個(gè)update操作為一個(gè)單獨(dú)的事務(wù))。
不過,值得注意的是,這里的所謂“自動(dòng)判定”,可能有些誤導(dǎo),ibatis 并沒有去
檢查當(dāng)前是否已經(jīng)有事務(wù)開啟,從而判斷當(dāng)前數(shù)據(jù)庫(kù)連接是否設(shè)定為自動(dòng)提交。
實(shí)際上,在執(zhí)行update語(yǔ)句時(shí),sqlMap會(huì)檢查當(dāng)前的Session是否已經(jīng)關(guān)聯(lián)了某個(gè)
數(shù)據(jù)庫(kù)連接,如果沒有,則取一個(gè)數(shù)據(jù)庫(kù)連接,將其AutoCommit屬性設(shè)為true,然后
執(zhí)行update 操作,執(zhí)行完之后又將這個(gè)連接釋放。這樣,上面兩次update 操作實(shí)際上
先后獲取了兩個(gè)數(shù)據(jù)庫(kù)連接,而不是我們通常所認(rèn)為的兩次update 操作都基于同一個(gè)
JDBC Connection。這點(diǎn)在開發(fā)時(shí)需特別注意。
對(duì)于多條SQL 組合而成的一個(gè)JDBC 事務(wù)操作而言,必須使用
startTransaction、commit和endTransaction操作以實(shí)現(xiàn)整體事務(wù)的原子性。
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
如:
try{
sqlMap = xmlBuilder.buildSqlMap(reader);
sqlMap.startTransaction();
User user = new User();
user.setId(new Integer(1));
user.setName("Erica");
user.setSex(new Integer(0));
sqlMap.update("User.updateUser", user);
User user2 = new User();
user2.setId(new Integer(2));
user2.setName("Kevin");
user2.setSex(new Integer(1));
sqlMap.update("User.updateUser", user2);
sqlMap.commitTransaction();
}finally{
sqlMap.endTransaction();
}
如果user1 或者user2的update操作失敗,整個(gè)事務(wù)就會(huì)在endTransaction時(shí)回
滾,從而保證了兩次update操作的原子性。
基于JTA的事務(wù)管理機(jī)制
JTA提供了跨數(shù)據(jù)庫(kù)連接(或其他JTA資源)的事務(wù)管理能力。這一點(diǎn)是與JDBC
Transaction最大的差異。
JDBC事務(wù)由Connnection管理,也就是說,事務(wù)管理實(shí)際上是在JDBC Connection
中實(shí)現(xiàn)。事務(wù)周期限于Connection的生命周期。同樣,對(duì)于基于JDBC的ibatis事務(wù)管
理機(jī)制而言,事務(wù)管理在SqlMapClient所依托的JDBC Connection中實(shí)現(xiàn),事務(wù)周期限
于SqlMapClient 的生命周期。
JTA事務(wù)管理則由 JTA容器實(shí)現(xiàn),JTA容器對(duì)當(dāng)前加入事務(wù)的眾多Connection進(jìn)
行調(diào)度,實(shí)現(xiàn)其事務(wù)性要求。JTA的事務(wù)周期可橫跨多個(gè)JDBC Connection生命周期。
同樣,對(duì)于基于JTA事務(wù)的ibatis而言,JTA事務(wù)橫跨可橫跨多個(gè)SqlMapClient。
下面這幅圖形象的說明了這個(gè)問題:
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
為了在ibatis中使用JTA事務(wù)管理,我們需要在配置文件中加以設(shè)定:
<transactionManager type="JTA">
……
</transactionManager>
在實(shí)際開發(fā)中,我們可能需要面對(duì)分布式事務(wù)的處理,如系統(tǒng)范圍內(nèi)包含了多個(gè)數(shù)據(jù)庫(kù),
也許還引入了JMS 上的事務(wù)管理(這在EAI 系統(tǒng)實(shí)現(xiàn)中非常常見)。我們就需要引入JTA
以實(shí)現(xiàn)系統(tǒng)范圍內(nèi)的全局事務(wù),如下面示例中,我們同時(shí)將user 對(duì)象更新到兩個(gè)不同的數(shù)
據(jù)庫(kù):
User user = new User();
user.setId(new Integer(1));
user.setName("Erica");
user.setSex(new Integer(0));
sqlMap1 = xmlBuilder.buildSqlMap(db1Config);
sqlMap2 = xmlBuilder.buildSqlMap(db2Config);
try{
sqlMap1.startTransaction();
sqlMap1.update("User.updateUser", user);
sqlMap2.update("User.updateUser", user);
sqlMap1. commitTransaction();
}finally{
sqlMap1.endTransaction();
}
上面的代碼中,兩個(gè)針對(duì)不同數(shù)據(jù)庫(kù)的SqlMapClient 實(shí)例,在同一個(gè)JTA 事務(wù)中
對(duì)user 對(duì)象所對(duì)應(yīng)的數(shù)據(jù)庫(kù)記錄進(jìn)行更新。外層的sqlMap1 啟動(dòng)了一個(gè)全局事務(wù),此
事務(wù)將涵蓋本線程內(nèi)commitTransaction 之前的所有數(shù)據(jù)庫(kù)操作。只要其間發(fā)生了
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
異常,則整個(gè)事務(wù)都將被回滾。
外部事務(wù)管理
基于JTA的事務(wù)管理還有另外一個(gè)特殊情況,就是利用外部事務(wù)管理機(jī)制。
對(duì)于外部事務(wù)管理,我們需要在配置文件中進(jìn)行如下設(shè)定:
<transactionManager type="EXTERNAL">
……
</transactionManager>
下面是一個(gè)外部事務(wù)管理的典型示例:
UserTransaction tx = new InitialContext().lookup(“……”);
……
sqlMap1 = xmlBuilder.buildSqlMap(db1Config);
sqlMap2 = xmlBuilder.buildSqlMap(db2Config);
sqlMap1.update("User.updateUser", user);
sqlMap2.update("User.updateUser", user);
……
tx.commit();
此時(shí),我們借助JTA UserTransaction實(shí)例啟動(dòng)了一個(gè)全局事務(wù)。之后的ibatis操作
(sqlMap1.update 和sqlMap2.update)全部被包含在此全局事務(wù)之中,當(dāng)
UserTransaction提交的時(shí)候,ibatis操作被包含在事務(wù)中提交,反之,如果UserTransaction
回滾,那么其間的ibatis操作也將作為事務(wù)的一部分被回滾。這樣,我們就實(shí)現(xiàn)了ibatis
外部的事務(wù)控制。
另一種外部事務(wù)管理方式是借助EJB 容器,通過EJB 的部署配置,我們可以指定
EJB方法的事務(wù)性
下面是一個(gè)Session Bean的doUpdate方法,它的事務(wù)屬性被申明為“Required”,
EJB容器將自動(dòng)維護(hù)此方法執(zhí)行過程中的事務(wù):
/**
* @ejb.interface-method
* view-type="remote"
*
* @ejb.transaction type = "Required"
**/
public void doUpdate(){
//EJB環(huán)境中,通過部署配置即可實(shí)現(xiàn)事務(wù)申明,而無需顯式調(diào)用事務(wù)
……
sqlMap1 = xmlBuilder.buildSqlMap(db1Config);
sqlMap2 = xmlBuilder.buildSqlMap(db2Config);
sqlMap1.update("User.updateUser", user);
sqlMap2.update("User.updateUser", user);
……
}//方法結(jié)束時(shí),如果沒有異常發(fā)生,則事務(wù)由EJB容器自動(dòng)提交。
上面的示例中,ibatis數(shù)據(jù)操作的事務(wù)管理將全部委托給EJB容器管理,由EJB
容器控制其事務(wù)調(diào)度。
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
在上面JTA事務(wù)管理的例子中,為了保持清晰,我們省略了startTransaction、
commitTransaction 和endTransaction 的編寫,在這種情況下,調(diào)用ibatis
的事務(wù)管理方法并非必須,不過在實(shí)際開發(fā)時(shí),請(qǐng)酌情添加startTransaction、
commitTransaction 和endTransaction 語(yǔ)句,這樣可以獲得更好的性能(如果
省略了startTransaction、commitTransaction 和endTransaction 語(yǔ)句,
ibatis將為每個(gè)數(shù)據(jù)操作獲取一個(gè)數(shù)據(jù)連接,就算引入了數(shù)據(jù)庫(kù)連接池機(jī)制,這樣的
無謂開銷也應(yīng)盡量避免,具體請(qǐng)參見JDBC事務(wù)管理中的描述),并保持代碼風(fēng)格的統(tǒng)
一。
Cache
在特定硬件基礎(chǔ)上(同時(shí)假設(shè)系統(tǒng)不存在設(shè)計(jì)上的缺漏和糟糕低效的SQL 語(yǔ)句)
Cache往往是提升系統(tǒng)性能的最關(guān)鍵因素)。
相對(duì)Hibernate 等封裝較為嚴(yán)密的ORM 實(shí)現(xiàn)而言(因?yàn)閷?duì)數(shù)據(jù)對(duì)象的操作實(shí)現(xiàn)
了較為嚴(yán)密的封裝,可以保證其作用范圍內(nèi)的緩存同步,而ibatis 提供的是半封閉
的封裝實(shí)現(xiàn),因此對(duì)緩存的操作難以做到完全的自動(dòng)化同步)。
ibatis 的緩存機(jī)制使用必須特別謹(jǐn)慎。特別是flushOnExecute 的設(shè)定(見
“ibatis配置”一節(jié)中的相關(guān)內(nèi)容),需要考慮到所有可能引起實(shí)際數(shù)據(jù)與緩存數(shù)據(jù)
不符的操作。如本模塊中其他Statement對(duì)數(shù)據(jù)的更新,其他模塊對(duì)數(shù)據(jù)的更新,甚
至第三方系統(tǒng)對(duì)數(shù)據(jù)的更新。否則,臟數(shù)據(jù)的出現(xiàn)將為系統(tǒng)的正常運(yùn)行造成極大隱患。
如果不能完全確定數(shù)據(jù)更新操作的波及范圍,建議避免Cache的盲目使用。
結(jié)合cacheModel來看:
<cacheModel
id="product-cache"
type ="LRU"
readOnly="true"
serialize="false">
</cacheModel>
可以看到,Cache有如下幾個(gè)比較重要的屬性:
Ø readOnly
Ø serialize
Ø type
readOnly
readOnly值的是緩存中的數(shù)據(jù)對(duì)象是否只讀。這里的只讀并不是意味著數(shù)據(jù)對(duì)象一
旦放入緩存中就無法再對(duì)數(shù)據(jù)進(jìn)行修改。而是當(dāng)數(shù)據(jù)對(duì)象發(fā)生變化的時(shí)候,如數(shù)據(jù)對(duì)
象的某個(gè)屬性發(fā)生了變化,則此數(shù)據(jù)對(duì)象就將被從緩存中廢除,下次需要重新從數(shù)據(jù)
庫(kù)讀取數(shù)據(jù),構(gòu)造新的數(shù)據(jù)對(duì)象。
而readOnly="false"則意味著緩存中的數(shù)據(jù)對(duì)象可更新,如user 對(duì)象的name
屬性發(fā)生改變。
只讀Cache能提供更高的讀取性能,但一旦數(shù)據(jù)發(fā)生改變,則效率降低。系統(tǒng)設(shè)計(jì)
時(shí)需根據(jù)系統(tǒng)的實(shí)際情況(數(shù)據(jù)發(fā)生更新的概率有多大)來決定Cache的讀寫策略。
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
serialize
如果需要全局的數(shù)據(jù)緩存,CacheModel的serialize屬性必須被設(shè)為true。否則數(shù)據(jù)
緩存只對(duì)當(dāng)前Session(可簡(jiǎn)單理解為當(dāng)前線程)有效,局部緩存對(duì)系統(tǒng)的整體性能提
升有限。
在serialize="true"的情況下,如果有多個(gè)Session同時(shí)從Cache 中讀取某個(gè)
數(shù)據(jù)對(duì)象,Cache 將為每個(gè)Session返回一個(gè)對(duì)象的復(fù)本,也就是說,每個(gè)Session 將
得到包含相同信息的不同對(duì)象實(shí)例。因而Session 可以對(duì)其從Cache 獲得的數(shù)據(jù)進(jìn)行
存取而無需擔(dān)心多線程并發(fā)情況下的同步?jīng)_突。
Cache Type:
與hibernate類似,ibatis通過緩沖接口的插件式實(shí)現(xiàn),提供了多種Cache的實(shí)現(xiàn)機(jī)
制可供選擇:
1. MEMORY
2. LRU
3. FIFO
4. OSCACHE
MEMORY類型Cache與WeakReference
MEMORY 類型的Cache 實(shí)現(xiàn),實(shí)際上是通過Java 對(duì)象引用進(jìn)行。ibatis 中,其實(shí)現(xiàn)類
為com.ibatis.db.sqlmap.cache.memory.MemoryCacheController,MemoryCacheController 內(nèi)部,
使用一個(gè)HashMap來保存當(dāng)前需要緩存的數(shù)據(jù)對(duì)象的引用。
這里需要注意的是Java2中的三種對(duì)象引用關(guān)系:
a SoftReference
b WeakReference
c PhantomReference
傳統(tǒng)的Java 對(duì)象引用,如:
public void doSomeThing(){
User user = new User()
……
}
當(dāng)doSomeThing方法結(jié)束時(shí),user 對(duì)象的引用丟失,其所占的內(nèi)存空間將由JVM在下
次垃圾回收時(shí)收回。如果我們將user 對(duì)象的引用保存在一個(gè)全局的HashMap中,如:
Map map = new HashMap();
public void doSomeThing(){
User user = new User();
map.put("user",user);
}
此時(shí),user 對(duì)象由于在map 中保存了引用,只要這個(gè)引用存在,那么JVM 永遠(yuǎn)也不會(huì)
收回user 對(duì)象所占用的內(nèi)存。
這樣的內(nèi)存管理機(jī)制相信諸位都已經(jīng)耳熟能詳,在絕大多數(shù)情況下,這幾乎是一種完美
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
的解決方案。但在某些情況下,卻有些不便。如對(duì)于這里的Cache 而言,當(dāng)user 對(duì)象使用
之后,我們希望保留其引用以供下次需要的時(shí)候可以重復(fù)使用,但又不希望這個(gè)引用長(zhǎng)期保
存,如果每個(gè)對(duì)象的引用都長(zhǎng)期保存下去的話,那隨著時(shí)間推移,有限的內(nèi)存空間將立即被
這些數(shù)據(jù)所消耗殆盡。最好的方式,莫過于有一種引用方式,可以在對(duì)象沒有被垃圾回收器
回收之前,依然能夠訪問此對(duì)象,當(dāng)垃圾回收器啟動(dòng)時(shí),如果此對(duì)象沒有被其他對(duì)象所使用,
則按照常規(guī)對(duì)其進(jìn)行回收。
SoftReference、WeakReference、PhantomReference為上面的思路提供了有力支持。
這三種類型的引用都屬于“非持續(xù)性引用”,也就是說,這種引用關(guān)系并非持續(xù)存在,
它們所代表的引用的生命周期與JVM 的運(yùn)行密切相關(guān),而非與傳統(tǒng)意義上的引用一樣依賴
于編碼階段的預(yù)先規(guī)劃。
先看一個(gè)SoftReference的例子:
SoftReference ref;
public void doSomeThing(){
User user = new User();
ref = new SoftReference(user);
}
public void doAnotherThing(){
User user = (User)ref.get();//通過SoftReference獲得對(duì)象引用
System.out.println(user.getName());
}
假設(shè)我們先執(zhí)行了doSomeThing 方法,產(chǎn)生了一個(gè)User 對(duì)象,并為其創(chuàng)建了一個(gè)
SoftReference引用。
之后的某個(gè)時(shí)刻,我們調(diào)用了doAnotherThing方法,并通過SoftReference獲取User 對(duì)
象的引用。
此時(shí)我們是否還能取得user 對(duì)象的引用?這要看JVM 的運(yùn)行情況。對(duì)于SoftReference
而言,只有當(dāng)目前內(nèi)存不足的情況下,JVM 在垃圾回收時(shí)才會(huì)收回其包含的引用(JVM 并
不是只有當(dāng)內(nèi)存不足時(shí)才啟動(dòng)垃圾回收機(jī)制,何時(shí)進(jìn)行垃圾回收取決于各版本JVM 的垃圾
回收策略。如某這垃圾回收策略為:當(dāng)系統(tǒng)目前較為空閑,且無效對(duì)象達(dá)到一定比率時(shí)啟動(dòng)
垃圾回收機(jī)制,此時(shí)的空余內(nèi)存倒可能還比較充裕)。這里可能出現(xiàn)兩種情況,即:
Ø JVM 目前還未出現(xiàn)過因內(nèi)存不足所引起的垃圾回收,user 對(duì)象的引用可以通過
SoftReference從JVM Heap中收回。
Ø JVM 已經(jīng)因?yàn)閮?nèi)存不足啟動(dòng)了垃圾回收機(jī)制,SoftReference所包含的user 對(duì)象的
引用被JVM 所廢棄。此時(shí)ref.get方法將返回一個(gè)空引用(null),對(duì)于上面
的代碼而言,也就意味著這里可能拋出一個(gè)NullPointerException。
WeakReference比SoftReference在引用的維持性上來看更加微弱。無需等到內(nèi)存不足的
情況,只要JVM 啟動(dòng)了垃圾回收機(jī)制,那么WeakReference所對(duì)應(yīng)的對(duì)象就將被JVM 回收。
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
也就是說,相對(duì)SoftReference而言,WeakReference 被JVM 回收的概率更大。
PhantomReference 比WeakReference 的引用維持性更弱。與WeakReference 和
SoftReference不同,PhantomReference所引用的對(duì)象幾乎無法被回收重用。它指向的對(duì)象實(shí)
際上已經(jīng)被JVM 銷毀(finalize方法已經(jīng)被執(zhí)行),只是暫時(shí)還沒被垃圾回收器收回而已。
PhantomReference主要用于輔助對(duì)象的銷毀過程,在實(shí)際應(yīng)用層研發(fā)中,幾乎不會(huì)涉及。
MEMORY類型的Cache正是借助SoftReference、WeakReference以及通常意義上的Java
Reference實(shí)現(xiàn)了對(duì)象的緩存管理。
下面是一個(gè)典型的MEMORY類型Cache配置:
<cacheModel id="user_cache" type="MEMORY">
<flushInterval hours="24"/>
<flushOnExecute statement="updateUser"/>
<property name="reference-type" value="WEAK" />
</cacheModel>
其中flushInterval 指定了多長(zhǎng)時(shí)間清除緩存,上例中指定每24 小時(shí)強(qiáng)行清空緩存
區(qū)的所有內(nèi)容。
reference-type屬性可以有以下幾種配置:
1. STRONG
即基于傳統(tǒng)的Java對(duì)象引用機(jī)制,除非對(duì)Cache顯式清空(如到了flushInterval
設(shè)定的時(shí)間;執(zhí)行了flushOnExecute所指定的方法;或代碼中對(duì)Cache執(zhí)行了清除
操作等),否則引用將被持續(xù)保留。
此類型的設(shè)定適用于緩存常用的數(shù)據(jù)對(duì)象,或者當(dāng)前系統(tǒng)內(nèi)存非常充裕的情況。
2. SOFT
基于SoftReference 的緩存實(shí)現(xiàn),只有JVM 內(nèi)存不足的時(shí)候,才會(huì)對(duì)緩沖池中的數(shù)
據(jù)對(duì)象進(jìn)行回收。
此類型的設(shè)定適用于系統(tǒng)內(nèi)存較為充裕,且系統(tǒng)并發(fā)量比較穩(wěn)定的情況。
3. WEAK
基于WeakReference 的緩存實(shí)現(xiàn),當(dāng)JVM 垃圾回收時(shí),緩存中的數(shù)據(jù)對(duì)象將被JVM
收回。
一般情況下,可以采用WEAK的MEMORY型Cache配置。
LRU型Cache
當(dāng)Cache達(dá)到預(yù)先設(shè)定的最大容量時(shí),ibatis會(huì)按照“最少使用”原則將使用頻率最少
的對(duì)象從緩沖中清除。
<cacheModel id="userCache" type="LRU">
<flushInterval hours="24"/>
<flushOnExecute statement="updateUser"/>
<property name="size" value="1000" />
</cacheModel>
可配置的參數(shù)有:
u flushInterval
指定了多長(zhǎng)時(shí)間清除緩存,上例中指定每24小時(shí)強(qiáng)行清空緩存區(qū)的所有內(nèi)容。
u size
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
Cache的最大容量。
FIFO型Cache
先進(jìn)先出型緩存,最先放入Cache中的數(shù)據(jù)將被最先廢除??膳渲脜?shù)與LRU型相同:
<cacheModel id="userCache" type="FIFO">
<flushInterval hours="24"/>
<flushOnExecute statement="updateUser"/>
<property name="size" value="1000" />
</cacheModel>
OSCache
與上面幾種類型的Cache不同,OSCache來自第三方組織Opensymphony??梢酝ㄟ^以
下網(wǎng)址獲得OSCache的最新版本(http://www.opensymphony.com/oscache/)。
在生產(chǎn)部署時(shí),建議采用OSCache,OSCache 是得到了廣泛使用的開源Cache 實(shí)現(xiàn)
(Hibernate 中也提供了對(duì)OSCache 的支持),它基于更加可靠高效的設(shè)計(jì),更重要的是,
最新版本的OSCache 已經(jīng)支持Cache 集群。如果系統(tǒng)需要部署在集群中,或者需要部署在
多機(jī)負(fù)載均衡模式的環(huán)境中以獲得性能上的優(yōu)勢(shì),那么OSCache在這里則是不二之選。
Ibatis中對(duì)于OSCache的配置相當(dāng)簡(jiǎn)單:
<cacheModel id="userCache" type="OSCACHE">
<flushInterval hours="24"/>
<flushOnExecute statement="updateUser"/>
<property name="size" value="1000" />
</cacheModel>
之所以配置簡(jiǎn)單,原因在于,OSCache擁有自己的配置文件(oscache.properties)J。
下面是一個(gè)典型的OSCache配置文件:
#是否使用內(nèi)存作為緩存空間
cache.memory=true
#緩存管理事件監(jiān)聽器,通過這個(gè)監(jiān)聽器可以獲知當(dāng)前Cache 的運(yùn)行情況
cache.event.listeners=com.opensymphony.oscache.plugins.clustersupport.JMSBroa
dcastingListener
#如果使用磁盤緩存(cache.memory=false),則需要指定磁盤存儲(chǔ)接口實(shí)現(xiàn)
#cache.persistence.class=com.opensymphony.oscache.plugins.diskpersistence.Disk
PersistenceListener
# 磁盤緩存所使用的文件存儲(chǔ)路徑
# cache.path=c:\\myapp\\cache
# 緩存調(diào)度算法,可選的有LRU,FIFO和無限緩存(UnlimitedCache)
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
# cache.algorithm=com.opensymphony.oscache.base.algorithm.FIFOCache
# cache.algorithm=com.opensymphony.oscache.base.algorithm.UnlimitedCache
cache.algorithm=com.opensymphony.oscache.base.algorithm.LRUCache
#內(nèi)存緩存的最大容量
cache.capacity=1000
# 是否限制磁盤緩存的容量
# cache.unlimited.disk=false
# 基于JMS的集群緩存同步配置
#cache.cluster.jms.topic.factory=java:comp/env/jms/TopicConnectionFactory
#cache.cluster.jms.topic.name=java:comp/env/jms/OSCacheTopic
#cache.cluster.jms.node.name=node1
# 基于JAVAGROUP的集群緩存同步配置
#cache.cluster.properties=UDP(mcast_addr=231.12.21.132;mcast_port=45566;ip_
ttl=32;mcast_send_buf_size=150000;mcast_recv_buf_size=80000):PING(timeout
=2000;num_initial_members=3):MERGE2(min_interval=5000;max_interval=10000
):FD_SOCK:VERIFY_SUSPECT(timeout=1500):pbcast.NAKACK(gc_lag=50;retransm
it_timeout=300,600,1200,2400,4800):pbcast.STABLE(desired_avg_gossip=20000):
UNICAST(timeout=5000):FRAG(frag_size=8096;down_thread=false;up_thread=fal
se):pbcast.GMS(join_timeout=5000;join_retry_timeout=2000;shun=false;print_loc
al_addr=true)
#cache.cluster.multicast.ip=231.12.21.132
配置好之后,將此文件放在CLASSPATH 中,OSCache 在初始化時(shí)會(huì)自動(dòng)找到此
文件并根據(jù)其中的配置創(chuàng)建緩存實(shí)例。
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
ibatis in Spring
這里我們重點(diǎn)探討Spring框架下的ibatis應(yīng)用,特別是在容器事務(wù)管理模式下的ibatis
應(yīng)用開發(fā)。
關(guān)于Spring Framework,請(qǐng)參見筆者另一篇文檔:
《Spring 開發(fā)指南》
http://www.xiaxin.net/Spring_Dev_Guide.rar針對(duì)ibatis,Spring配置文件如下:
Ibatis-Context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"
<beans>
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName">
<value>net.sourceforge.jtds.jdbc.Driver</value>
</property>
<property name="url">
<value>jdbc:jtds:sqlserver://127.0.0.1:1433/Sample</value>
</property>
<property name="username">
<value>test</value>
</property>
<property name="password">
<value>changeit</value>
</property>
</bean>
<bean id="sqlMapClient"
class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation">
<value>SqlMapConfig.xml</value>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactio
nManager">
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
<property name="dataSource">
<ref local="dataSource"/>
</property>
</bean>
<bean id="userDAO" class="net.xiaxin.dao.UserDAO">
<property name="dataSource">
<ref local="dataSource" />
</property>
<property name="sqlMapClient">
<ref local="sqlMapClient" />
</property>
</bean>
<bean id="userDAOProxy"
class="org.springframework.transaction.interceptor.TransactionPro
xyFactoryBean">
<property name="transactionManager">
<ref bean="transactionManager" />
</property>
<property name="target">
<ref local="userDAO" />
</property>
<property name="transactionAttributes">
<props>
<prop key="insert*">PROPAGATION_REQUIRED</prop>
<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
</bean>
</beans>
可以看到:
1. sqlMapClient節(jié)點(diǎn)
sqlMapClient節(jié)點(diǎn)實(shí)際上配置了一個(gè)sqlMapClient的創(chuàng)建工廠類。
configLocation屬性配置了ibatis映射文件的名稱。
2. transactionManager節(jié)點(diǎn)
transactionManager采用了Spring中的DataSourceTransactionManager。
3. userDAO節(jié)點(diǎn)
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
對(duì)應(yīng)的,UserDAO需要配置兩個(gè)屬性,sqlMapClient和DataSource,
sqlMapClient將從指定的DataSource中獲取數(shù)據(jù)庫(kù)連接。
本例中Ibatis映射文件非常簡(jiǎn)單:
sqlMapConfig.xml:
<sqlMapConfig>
<sqlMap resource="net/xiaxin/dao/entity/user.xml"/>
</sqlMapConfig>
net/xiaxin/dao/entity/user.xml
<sqlMap namespace="User">
<typeAlias alias="user" type="net.xiaxin.dao.entity.User" />
<insert id="insertUser" parameterClass="user">
INSERT INTO users ( username, password) VALUES ( #username#,
#password# )
</insert>
</sqlMap>
UserDAO.java:
public class UserDAO extends SqlMapClientDaoSupport implements
IUserDAO {
public void insertUser(User user) {
getSqlMapClientTemplate().update("insertUser", user);
}
}
SqlMapClientDaoSupport(如果使用ibatis 1.x版本,對(duì)應(yīng)支持類是
SqlMapDaoSupport)是Spring中面向ibatis的輔助類,它負(fù)責(zé)調(diào)度DataSource、
SqlMapClientTemplate(對(duì)應(yīng)ibatis 1.x版本是SqlMapTemplate)完成ibatis操作,
而DAO則通過對(duì)此類進(jìn)行擴(kuò)展獲得上述功能。上面配置文件中針對(duì)UserDAO的屬性設(shè)
置部分,其中的屬性也是繼承自于這個(gè)基類。
SqlMapClientTemplate對(duì)傳統(tǒng)SqlMapClient調(diào)用模式進(jìn)行了封裝,簡(jiǎn)化了上層訪問
代碼。
User.java:
public class User {
public Integer id;
public String username;
IBATIS Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
public String password;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
測(cè)試代碼:
InputStream is = new FileInputStream("Ibatis-Context.xml");
XmlBeanFactory factory = new XmlBeanFactory(is);
IUserDAO userdao = (IUserDAO)factory.getBean("userDAOProxy");
User user = new User();
user.setUsername("Sofia");
user.setPassword("mypass");
userdao.insertUser(user);
對(duì)比前面ibatis 程序代碼,我們可以發(fā)現(xiàn)UserDAO.java 變得異常簡(jiǎn)潔,這也正是
Spring Framework為我們帶來的巨大幫助。
Spring以及其他IOC 框架對(duì)傳統(tǒng)的應(yīng)用框架進(jìn)行了顛覆性的革新,也許這樣的評(píng)價(jià)有
點(diǎn)過于煽情,但是這確是筆者第一次跑通Spring HelloWorld 時(shí)的切身感受。有興趣的
讀者可以研究一下Spring framework,enjoy it!