IBATIS最新最全開發指南 - 通俗易懂IBATIS教程(1)

ibatis 開發指南
ibatis Quick Start......................................... 5
準備工作............................................... 5
構建ibatis 基礎代碼...................................... 5
ibatis 配置.............................................. 11
ibatis 基礎語義............................................ 16
XmlSqlMapClientBuilder....................................... 16
SqlMapClient .............................................. 16
SqlMapClient 基本操作示例................................. 16
OR 映射................................................ 19
ibatis 高級特性...................................... 26
數據關聯.......................................... 26
一對多關聯....................................... 26
一對一關聯...................................... 28
延遲加載.................................... 30
動態映射..................................... 31
事務管理........................................... 35


基於JDBC 的事務管理機制.................................... 35
基於JTA 的事務管理機制................................ 36
外部事務管理............................................ 38
Cache .......................................... 39
MEMORY 類型Cache 與WeakReference .................. 40
LRU 型Cache ............................................... 42
FIFO 型Cache ....................................... 43
OSCache.............................................. 43

 

ibatis 開發指南
相對Hibernate 和Apache OJB 等“一站式”ORM 解決方案而言,ibatis 是一種“半
自動化”的ORM 實現。
所謂“半自動”,可能理解上有點生澀。縱觀目前主流的ORM ,無論Hibernate 還是
Apache OJB,都對數據庫結構提供了較爲完整的封裝,提供了從POJO 到數據庫表的全
套映射機制。程序員往往只需定義好了POJO 到數據庫表的映射關係,即可通過Hibernate
或者OJB 提供的方法完成持久層操作。程序員甚至不需要對SQL 的熟練掌握,
Hibernate/OJB 會根據制定的存儲邏輯,自動生成對應的SQL 並調用JDBC 接口加以執
行。
大多數情況下( 特別是對新項目,新系統的開發而言)
,這樣的機制無往不利,大有一
統天下的勢頭。但是,在一些特定的環境下,這種一站式的解決方案卻未必靈光。
在筆者的系統諮詢工作過程中,常常遇到以下情況:
1. 系統的部分或全部數據來自現有數據庫,處於安全考慮,只對開發團隊提供幾
條Select SQL(或存儲過程)以獲取所需數據,具體的表結構不予公開。
2. 開發規範中要求, 所有牽涉到業務邏輯部分的數據庫操作,必須在數據庫層由
存儲過程實現(就筆者工作所面向的金融行業而言,工商銀行、中國銀行、交

 


通銀行,都在開發規範中嚴格指定)
3. 系統數據處理量巨大,性能要求極爲苛刻,這往往意味着我們必須通過經過高
度優化的SQL 語句(或存儲過程)才能達到系統性能設計指標。
面對這樣的需求,再次舉起Hibernate 大刀,卻發現刀鋒不再銳利,甚至無法使用,
奈何?恍惚之際,只好再摸出JDBC 準備拼死一搏……,說得未免有些淒涼,直接使用JDBC
進行數據庫操作實際上也是不錯的選擇,只是拖沓的數據庫訪問代碼,乏味的字段讀取操作
令人厭煩。
“半自動化”的ibatis,卻剛好解決了這個問題。
這裏的“半自動化”,是相對Hibernate 等提供了全面的數據庫封裝機制的“全自動化”
ORM 實現而言,“全自動”ORM 實現了POJO 和數據庫表之間的映射,以及SQL 的自動
生成和執行。而ibatis 的着力點,則在於POJO 與SQL 之間的映射關係。也就是說,ibatis
並不會爲程序員在運行期自動生成SQL 執行。具體的SQL 需要程序員編寫,然後通過映
射配置文件,將SQL 所需的參數,以及返回的結果字段映射到指定POJO 。
使用ibatis 提供的ORM 機制,對業務邏輯實現人員而言,面對的是純粹的Java 對象,
這一層與通過Hibernate 實現ORM 而言基本一致,而對於具體的數據操作,Hibernate
會自動生成SQL 語句,而ibatis 則要求開發者編寫具體的SQL 語句。相對Hibernate 等
“全自動”ORM 機制而言,ibatis 以SQL 開發的工作量和數據庫移植性上的讓步,爲系統
設計提供了更大的自由空間。作爲“全自動”ORM 實現的一種有益補充,ibatis 的出現顯
得別具意義。

 

ibatis Quick Start
準備工作
1. 下載ibatis 軟件包(http://www.ibatis.com)。
2. 創建測試數據庫,並在數據庫中創建一個t_user 表,其中包含三個字段:
. id(int)
. name(varchar)
. sex(int) 。
3. 爲了在開發過程更加直觀,我們需要將ibatis 日誌打開以便觀察ibatis 運作的細節。
ibatis 採用Apache common_logging,並結合Apache log4j 作爲日誌輸出組件。在
CLASSPATH 中新建log4j.properties 配置文件,內容如下:
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
構建ibatis 基礎代碼
ibatis 基礎代碼包括:
1. ibatis 實例配置
一個典型的配置文件如下(具體配置項目的含義見後):
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMapConfig
PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"


"http://www.ibatis.com/dtd/sql-map-config-2.dtd">
<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" ="user"/>
<property name="JDBC.Password" ="mypass"/>
<property name=
value="10"/>
<property name=value="5"/>
<property name=
value="120000"/>
<property name="Pool.TimeToWait" ="500"/>
<property name="Pool.PingQuery" ="select 1 from
ACCOUNT"/>
<property name="Pool.PingEnabled" ="false"/>
<property name=
value="1"/>
<property name=
value="1"/>
</dataSource>
</transactionManager>
<sqlMap resource="com/ibatis/sample/User.xml"/>


</sqlMapConfig>
valuevalue"Pool.MaximumActiveConnections"
"Pool.MaximumIdleConnections"
"Pool.MaximumCheckoutTime"
valuevaluevalue"Pool.PingConnectionsOlderThan"
"Pool.PingConnectionsNotUsedFor"
2. POJO(Plain Ordinary Java Object)
下面是我們用作示例的一個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;

 

}
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 不同。因爲需要人工編寫SQL 代碼,ibatis 的映射文件一般採
用手動編寫(通過Copy/Paste,手工編寫映射文件也並沒想象中的麻煩)。
針對上面POJO 的映射代碼如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMap
PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-2.dtd">
<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

 

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>四個節點,我們分別定義了針對TUser 對象的增刪改查操作。在這
四個節點中,我們指定了對應的SQL 語句,以update 節點爲例:
……
<update id="updateUser" ⑴
parameterClass="user"> ⑵
<![CDATA[ ⑶

 


UPDATE t_user ⑷
SET (

 

 

IBATIS Developer’s Guide Version 1.0
name=#name#, ⑸
sex=#sex# ⑹
)
WHERE id = #id# ⑺
]]>
</update>
……
⑴ ID
指定了操作ID,之後我們可以在代碼中通過指定操作id 來執行此節點所定
義的操作,如:
sqlMap.update("updateUser",user);
ID 設定使得在一個配置文件中定義兩個同名節點成爲可能(兩個update 節
點,以不同id 區分)
⑵ parameterClass
指定了操作所需的參數類型,此例中update 操作以
com.ibatis.sample.User 類型的對象作爲參數,目標是將提供的User
實例更新到數據庫。
parameterClass="user"中,user 爲“com.ibatis.sample.User”
類的別名,別名可通過typeAlias 節點指定,如示例配置文件中的:
<typeAlias alias="user" type="com.ibatis.sample.User"/>
⑶ <![CDATA[……]]>
通過<![CDATA[……]]>節點,可以避免SQL 中與XML 規範相沖突的字符對
XML 映射文件的合法性造成影響。
⑷ 執行更新操作的SQL,這裏的SQL 即實際數據庫支持的SQL 語句, 將由


ibatis 填入參數後交給數據庫執行。
⑸ SQL 中所需的用戶名參數,
“#name#”在運行期會由傳入的user 對象的name
屬性填充。
⑹ SQL 中所需的用戶性別參數“#sex#”, 將在運行期由傳入的user 對象的
sex 屬性填充。
⑺ SQL 中所需的條件參數“#id#”, 將在運行期由傳入的user 對象的id 屬性
填充。
對於這個示例,ibatis 在運行期會讀取id 爲“updateUser”的update 節點
的SQL 定義,並調用指定的user 對象的對應getter 方法獲取屬性值,並用此
屬性值,對SQL 中的參數進行填充後提交數據庫執行。
此例對應的應用級代碼如下,其中演示了的基本使用方法:
String resource ="com/ibatis/sample/SqlMapConfig.xml";
Reader reader;
ibatis SQLMap

 

reader = Resources.getResourceAsReader(resource);
XmlSqlMapClientBuilder xmlBuilder =
new XmlSqlMapClientBuilder();
SqlMapClient sqlMap = xmlBuilder.buildSqlMap(reader);
//sqlMap系統初始化完畢,開始執行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 運作的核心,所有操作均通過SqlMapClient
實例完成。
可以看出,對於應用層而言,程序員面對的是傳統意義上的數據對象,而非JDBC
中煩雜的ResultSet,這使得上層邏輯開發人員的工作量大大減輕,同時代碼更
加清晰簡潔。
數據庫操作在映射文件中加以定義,從而將數據存儲邏輯從上層邏輯代碼中獨立
出來。
而底層數據操作的SQL 可配置化,使得我們可以控制最終的數據操作方式,通過
SQL 的優化獲得最佳的數據庫執行效能,這在依賴SQL 自動生成的“全自動”ORM


機制中是所難以實現的。
 

ibatis 配置
結合上面示例中的ibatis 配置文件。下面是對配置文件中各節點的說明:
<?xml version="1.0" encoding="UTF-8" ?>
PUBLIC
">
<sqlMapConfig>
<settings ⑴
cacheModelsEnabled=
enhancementEnabled=
lazyLoadingEnabled=
errorTracingEnabled=
maxRequests=
maxSessions=
maxTransactions=
useStatementNamespaces=
/>
<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" ="user"/>
<property name="JDBC.Password" ="mypass"/>
<property name=
value="10"/>
<property name=value="5"/>
<property name=
value="120000"/>
<property name="Pool.TimeToWait" ="500"/>
<property name="Pool.PingQuery" ="select 1 from
ACCOUNT"/>
<property name="Pool.PingEnabled" ="false"/>
<property name=
value="1"/>
<property name=
value="1"/>
</dataSource>
<!DOCTYPE sqlMapConfig
"-//iBATIS.com//DTD SQL Map Config 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-config-2.dtd"true"
"true"
"true"
"true"
"32"
"10"
"5"
"false"
valuevalue"Pool.MaximumActiveConnections"
"Pool.MaximumIdleConnections"
"Pool.MaximumCheckoutTime"
valuevaluevalue"Pool.PingConnectionsOlderThan"


"Pool.PingConnectionsNotUsedFor"

 

 

</transactionManager>
<sqlMap resource="com/ibatis/sample/User.xml"/> ⑷
<sqlMap resource="com/ibatis/sample/Address.xml"/>
</sqlMapConfig>
⑴ Settings 節點
參數描述
cacheModelsEnabled 是否啓用SqlMapClient 上的緩存機制。
建議設爲"true"
enhancementEnabled 是否針對POJO 啓用字節碼增強機制以提升
getter/setter 的調用效能,避免使用Java
Reflect 所帶來的性能開銷。
同時,這也爲Lazy Loading 帶來了極大的性能
提升。
建議設爲"true"
errorTracingEnabled 是否啓用錯誤日誌,在開發期間建議設爲"true"
以方便調試
lazyLoadingEnabled 是否啓用延遲加載機制,建議設爲"true"
maxRequests 最大併發請求數(Statement 併發數)
maxTransactions 最大併發事務數
maxSessions 最大Session 數。即當前最大允許的併發
SqlMapClient 數。
maxSessions 設定必須介於
maxTransactions 和maxRequests 之間,即
maxTransactions<maxSessions=<
maxRequests
useStatementNamespaces 是否使用Statement 命名空間。
這裏的命名空間指的是映射文件中,sqlMap 節點
的namespace 屬性,如在上例中針對t_user
表的映射文件sqlMap 節點:
<sqlMap namespace="User">
這裏,指定了此sqlMap 節點下定義的操作均從
屬於"User"命名空間。
在useStatementNamespaces="true"的情
況下,Statement 調用需追加命名空間,如:

 

⑵ transactionManager 節點
sqlMap.update("User.updateUser",use
r);
否則直接通過Statement 名稱調用即
sqlMap.update("updateUser",user);
但請注意此時需要保證所有映射
定義無重名。
可,如:
文件中,
Statement
transactionManager 節點定義了ibatis 的事務管理器,目前提供了以下幾
種選擇:
. JDBC
通過傳統JDBC Connection.commit/rollback 實現事務支持。
. JTA
使用容器提供的JTA 服務實現全局事務管理。
. EXTERNAL
外部事務管理, 如在EJB 中使用ibatis,通過EJB 的部署配置即可實現自
動的事務管理機制。此時ibatis 將把所有事務委託給外部容器進行管理。
此外,通過Spring 等輕量級容器實現事務的配置化管理也是一個不錯的選
擇。關於結合容器實現事務管理,參見“高級特性”中的描述。
⑶ dataSource 節點
dataSource 從屬於transactionManager 節點,用於設定ibatis 運行期使
用的DataSource 屬性。
type 屬性: dataSource 節點的type屬性指定了dataSource 的實現類型。
可選項目:
. SIMPLE:

 


SIMPLE 是ibatis 內置的dataSource 實現,其中實現了一個簡單的
數據庫連接池機制,對應ibatis 實現類爲
com.ibatis.sqlmap.engine.datasource.SimpleDataSourceFactory 。
. DBCP:
基於Apache DBCP 連接池組件實現的DataSource 封裝,當無容器提
供DataSource 服務時,建議使用該選項,對應ibatis 實現類爲
com.ibatis.sqlmap.engine.datasource.DbcpDataSourceFactory 。
. JNDI:
使用J2EE 容器提供的DataSource 實現,DataSource 將通過指定
的JNDI Name 從容器中獲取。對應ibatis 實現類爲
com.ibatis.sqlmap.engine.datasource.JndiDataSourceFacto
ry。
dataSource 的子節點說明(SIMPLE&DBCP):

 

參數描述
JDBC.Driver JDBC 驅動。
如:org.gjt.mm.mysql.Driver
JDBC.ConnectionURL 數據庫URL 。
如:jdbc:mysql://localhost/sample
如果用的是SQLServer JDBC Driver,需要
在url 後追加SelectMethod=Cursor 以獲得
JDBC 事務的多Statement 支持。
JDBC.Username 數據庫用戶名
JDBC.Password 數據庫用戶密碼
Pool.MaximumActiveConn 數據庫連接池可維持的最大容量。
ections
Pool.MaximumIdleConnec
tions
數據庫連接池中允許的掛起(idle)連接數。
以上子節點適用於SIMPLE 和DBCP 模式,分別針對SIMPLE 和DBCP 模式的
DataSource 私有配置節點如下:
SIMPLE:
參數描述
Pool.MaximumCheckoutTi 數據庫聯接池中,連接被某個任務所允許佔用的
me 最大時間,如果超過這個時間限定,連接將被強
制收回。(毫秒)
Pool.TimeToWait 當線程試圖從連接池中獲取連接時,連接池中無
可用連接可供使用,此時線程將進入等待狀態,
直到池中出現空閒連接。此參數設定了線程所允
許等待的最長時間。(毫秒)
Pool.PingQuery 數據庫連接狀態檢測語句。
某些數據庫在連接在某段時間持續處於空閒狀態
時會將其斷開。而連接池管理器將通過此語句檢
測池中連接是否可用。
檢測語句應該是一個最簡化的無邏輯SQL 。
如“select 1 from t_user”, 如果執行此語句
成功,連接池管理器將認爲此連接處於可用狀態。
Pool.PingEnabled 是否允許檢測連接狀態。
Pool.PingConnectionsOl 對持續連接時間超過設定值(毫秒)的連接進行
derThan 檢測。

 

 

Pool.PingConnectionsNo
tUsedFor
對空閒超過設定值(毫秒)的連接進行檢測。
DBCP:
參數描述
Pool.MaximumWait 當線程試圖從連接池中獲取連接時,連接池中無
可用連接可供使用,此時線程將進入等待狀態,
直到池中出現空閒連接。此參數設定了線程所允
許等待的最長時間。(毫秒)
Pool.ValidationQuery 數據庫連接狀態檢測語句。
某些數據庫在連接在某段時間持續處於空閒狀態
時會將其斷開。而連接池管理器將通過此語句檢
測池中連接是否可用。
檢測語句應該是一個最簡化的無邏輯SQL 。
如“select 1 from t_user”, 如果執行此語句
成功,連接池管理器將認爲此連接處於可用狀態。
Pool.LogAbandoned 當數據庫連接被廢棄時,是否打印日誌。
Pool.RemoveAbandonedTi 數據庫連接被廢棄的最大超時時間
meout
Pool.RemoveAbandoned 當連接空閒時間超過
RemoveAbandonedTimeout 時,是否將其廢
棄。
JNDI 由於大部分配置是在應用服務器中進行,因此ibatis 中的配置相對簡單,下面
是分別使用JDBC 和JTA 事務管理的JDNI 配置:
使用JDBC 事務管理的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>

 

 

</transactionManager>
⑷ sqlMap 節點
sqlMap 節點指定了映射文件的位置,配置中可出現多個sqlMap 節點,以指定
項目內所包含的所有映射文件。
ibatis 基礎語義
XmlSqlMapClientBuilder
XmlSqlMapClientBuilder 是ibatis 2.0 之後版本新引入的組件,用以替代1.x
版本中的XmlSqlMapBuilder。其作用是根據配置文件創建SqlMapClient 實例。
SqlMapClient
SqlMapClient 是ibatis 的核心組件,提供數據操作的基礎平臺。SqlMapClient
可通過XmlSqlMapClientBuilder 創建:
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文件在
中的相對路徑。XmlSqlMapClientBuilder 通過接受一個Reader 類型的配置文
件句柄,根據配置參數,創建SqlMapClient 實例。
SqlMapClient 提供了衆多數據操作方法,下面是一些常用方法的示例,具體說明
文檔請參見ibatis java doc,或者ibatis 官方開發手冊。
SqlMapClient 基本操作示例
以下示例摘自ibatis 官方開發手冊,筆者對其進行了重新排版以獲得更好的閱讀效果。
例1: 數據寫入操作(insert, update, delete):
sqlMap.startTransaction();
Product product = new Product();
product.setId (1);
product.setDescription (“Shih Tzu”);
int rows = sqlMap.insert (“insertProduct”, product);
sqlMap.commitTransaction();

例2: 數據查詢(select)
sqlMap.startTransaction();
Integer key = new Integer (1);
Product product = (Product)sqlMap.queryForObject
(“getProduct”, key);
sqlMap.commitTransaction();
例3: 在指定對象中存放查詢結果(select)
sqlMap.startTransaction();
Customer customer = new Customer();
sqlMap.queryForObject(“getCust”, parameterObject, customer);
sqlMap.queryForObject(“getAddr”, parameterObject, customer);
sqlMap.commitTransaction();
例4: 執行批量查詢(select)
sqlMap.startTransaction();
List list = sqlMap.queryForList (“getProductList”, null);
sqlMap.commitTransaction();
例5: 關於AutoCommit
//沒有預先執行startTransaction時,默認爲模式
int rows = sqlMap.insert (“insertProduct”, product);
auto_commit
例6:查詢指定範圍內的數據
sqlMap.startTransaction();
= “”, nullsqlMap.commitTransaction();
List list sqlMap.queryForList (getProductList, 0, 40);

 


例7: 結合RowHandler 進行查詢(select)
MyRowHandler implements RowHandler {
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();
public classpublic void

 

 

例8: 分頁查詢(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”);

 

 

OR 映射
相對Hibernate 等ORM 實現而言,ibatis 的映射配置更爲簡潔直接,下面是一
個典型的配置文件。
<!DOCTYPE sqlMap
PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-2.dtd">
<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>

 

</sqlMap>
可以看到,映射文件主要分爲兩個部分:模塊配置和Statement 配置。
模塊配置包括:
. typeAlias 節點:
定義了本映射文件中的別名,以避免過長變量值的反覆書寫,此例中通過
typeAlias 節點爲類"com.ibatis.sample.User"定義了一個別名"user",
這樣在本配置文件的其他部分,需要引用"com.ibatis.sample.User" 類時,
只需以其別名替代即可。
. cacheModel 節點
定義了本映射文件中使用的Cache 機制:
<cacheModel id="userCache" type="LRU">
<flushInterval hours="24"/>
<flushOnExecute statement="updateUser"/>
<property name="size" value="1000" />
</cacheModel>
這裏申明瞭一個名爲"userCache" 的,之後可以在cacheModel
Statement 申明中對其進行引用:
<select id="getUser"
parameterClass="java.lang.String"
resultClass="user"
cacheModel="userCache"
>

 


這表明對通過id 爲"getUser"的獲取的數據,使用Select statement
cacheModel "userCache" 進行緩存。之後如果程序再次用此Statement
進行數據查詢,即直接從緩存中讀取查詢結果,而無需再去數據庫查詢。
cacheModel 主要有下面幾個配置點:
. flushInterval :
設定緩存有效期,如果超過此設定值,則將此CacheModel 的緩存清空。
. size:
本CacheModel 中最大容納的數據對象數量。
. flushOnExecute:
指定執行特定Statement 時,將緩存清空。如updateUser 操作將更
新數據庫中的用戶信息,這將導致緩存中的數據對象與數據庫中的實際
數據發生偏差,因此必須將緩存清空以避免髒數據的出現。
關於Cache 的深入探討,請參見“高級特性”中的相關章節。
Statement 配置:

 

Statement 配置包含了數個與SQL Statement 相關的節點,分別爲:
. statement
. insert
. delete
. update
. select
. procedure
其中,statement 最爲通用,它可以替代其餘的所有節點。除statement 之外
的節點各自對應了SQL 中的同名操作(procedure 對應存儲過程)。
使用statement 定義所有操作固然可以達成目標,但缺乏直觀性,建議在實際
開發中根據操作目的,各自選用對應的節點名加以申明。一方面,使得配置文件
更加直觀,另一方面,也可藉助DTD 對節點申明進行更有針對性的檢查,以避免
配置上的失誤。
各種類型的Statement 配置節點的參數類型基本一致,區別在於數量不同。如
insert、update、delete 節點無需返回數據類型定義(總是int)。
主要的配置項如下:
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:

 

 

<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>
其中以“[]”包圍的部分爲可能出現的配置欄目。
參數描述
parameterClass 參數類。指定了參數的完整類名(包括包路徑)。
可通過別名避免每次重複書寫冗長的類名。
resultClass 結果類。指定結果類型的完整類名(包括包路徑)
可通過別名避免每次重複書寫冗長的類名。
parameterMap 參數映射,需結合parameterMap 節點對映射
關係加以定義。
對於存儲過程之外的statement 而言,建議使用
parameterClass 作爲參數配置方式, 一方面避

 

 

免了參數映射配置工作,另一方面其性能表現也
更加出色。
resultMap 結果映射,需結合resultMap 節點對映射關係
加以定義。
cacheModel statement 對應的Cache 模塊。
對於參數定義而言,儘量使用parameterClass,即直接將POJO 作爲
statement 的調用參數,這樣在SQL 中可以直接將POJO 的屬性作爲參數加以
設定,如:
<update id="updateUser"
parameterClass="com.ibatis.sample.User">
UPDATE t_user
SET
name=#name#,
sex=#sex#
WHERE id = #id#
</update>
這裏將com.ibatis.sample.User 類設定爲的參數,之後,update statement
我們即可在SQL 中通過#propertyName#對POJO 的屬性進行引用。如上例
中的:
SET name=#name#, sex=#sex# WHERE id=#id#
運行期,將通過調用對象的、和方法獲得相ibatis User getName getSex getId
應的參數值,並將其作爲SQL 的參數。
如果parameterClass 中設定的是jdk 的中的簡單對象類型,如String、
Integer,ibatis 會直接將其作爲SQL 中的參數值。
我們也可以將包含了參數數據的Map 對象傳遞給Statement ,如:
<update id="updateUser"
parameterClass="java.util.Map">
UPDATE t_user
SET
name=#name#,
sex=#sex#
WHERE id = #id#
</update>
這裏傳入的參數就是一個對象,將以””、””、”id”從中Map ibatis key namesex
提取對應的參數值。
同樣的原理,我們也可以在resultMap 中設定返回類型爲map 。
<select id="getUser"
parameterClass="java.lang.String"
resultClass="java.util.Map">

 

 

<![CDATA[
select
id,
name,
sex
from t_user
where id = #id#
]]>
</select>
返回的結果將以各字段名爲保存在對象中返回。key Map
在SQL 中設定參數名時,可以同時指定參數類型, 如:
SET name=#name:VARCHAR#,sex=#sex:NUMERIC# WHERE
id=#id:NUMERIC#
對於返回結果而言,如果是select 語句,建議也採用resultClass 進行定義,如:
<select id="getUser"
parameterClass="java.lang.String"
resultClass="user">
<![CDATA[
select
name,
sex
from t_user
where name = #name#
]]>
</select>
會自動根據語句中的字段名,調用對應的方法設定屬性ibatis select POJO set
值,如上例中,ibatis 會調用setName,setSex 方法將Select 語句返回的數據賦
予相應的POJO 實例。
有些時候,數據庫表中的字段名過於晦澀,而爲了使得代碼更易於理解,我們
希望字段映射到POJO 時,採用比較易讀的屬性名,此時,我們可以通過Select
的as 字句對字段名進行轉義,如(假設我們的書庫中對應用戶名的字段爲

 


xingming ,對應性別的字段爲xingbie):
select
xingming as name,
xingbie as sex
from t_user
where id = #id#
會根據轉義後的字段名進行屬性映射(即調用的方法而ibatis POJO setName
不是setXingming 方法)。

 

 

parameterMap 和resultMap 實現了POJO 到數據庫字段的映射配置,下面是
一個例子:
<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 指定了如果參數爲空(null)時的默認值。
之後我們即可在statement 申明中對其進行引用,如:
<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>
一般而言,對於insert 、update 、delete 、select 語句,優先採用parameterClass
和resultClass 。
parameterMap 使用較少,而resultMap 則大多用於嵌套查詢以及存儲過程的

 

處理,之所以這樣,原因是由於存儲過程相對而言比較封閉(很多情況下需要調用現有
的存儲過程,其參數命名和返回的數據字段命名往往不符合Java 編程中的命名習慣,
並且由於我們難以通過Select SQL 的as子句進行字段名轉義,無法使其自動與POJO
中的屬性名相匹配)。此時,使用resultMap 建立字段名和POJO 屬性名之間的映射
關係就顯得非常有效。另一方面,由於通過resultMap 指定了字段名和字段類型,
ibatis 無需再通過JDBC ResultSetMetaData 來動態獲取字段信息,在一定程度
上也提升了性能表現。
ibatis 高級特性
數據關聯
至此,我們討論的都是針對獨立數據的操作。在實際開發中,我們常常遇到關聯數
據的情況,如User 對象擁有若干Address 對象,每個Address 對象描述了對應User 的
一個聯繫地址,這種情況下,我們應該如何處理?
通過單獨的Statement 操作固然可以實現(通過Statement 用於讀取用戶數據,再手
工調用另外一個Statement 根據用戶ID 返回對應的Address 信息)。不過這樣未免失之
繁瑣。下面我們就看看在ibatis 中,如何對關聯數據進行操作。
ibatis 中,提供了Statement 嵌套支持,通過Statement 嵌套,我們即可實現關聯數
據的操作。
一對多關聯
下面的例子中,我們首選讀取t_user 表中的所有用戶記錄,然後獲取每個用戶對應
的所有地址信息。
配置文件如下:
<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,

 

 

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>
對應代碼:
String resource ="com/ibatis/sample/SqlMapConfig.xml";
Reader reader;
reader = Resources.getResourceAsReader(resource);
XmlSqlMapClientBuilder xmlBuilder= new XmlSqlMapClientBuilder();
sqlMap = xmlBuilder.buildSqlMap(reader);
//sqlMap系統初始化完畢
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());
}
}

 

 

這裏通過在resultMap 中定義嵌套查詢getAddressByUserId,我們實現了關聯
數據的讀取。
實際上,這種方式類似於前面所說的通過兩條單獨的Statement 進行關聯數據的讀
取,只是將關聯關係在配置中加以描述,由ibatis 自動完成關聯數據的讀取。
需要注意的是,這裏有一個潛在的性能問題,也就是所謂“n+1”Select 問題。
注意上面示例運行過程中的日誌輸出:
……
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 (目前t_user 表中有兩
條測試數據),隨即,通過兩次Select 操作,從t_address 表中讀取兩個用戶所關聯的
Address 記錄。
如果t_user 表中記錄較少,不會有明顯的影響,假設t_user 表中有十萬條記錄,那


麼這樣的操作將需要100000+1 條Select 語句反覆執行才能獲得結果,無疑,隨着記錄
的增長,這樣的開銷將無法承受。
之所以在這裏提及這個問題,目的在於引起讀者的注意,在系統設計中根據具體情
況,採用一些規避手段(如使用存儲過程集中處理大批量關聯數據),從而避免因爲這
個問題而引起產品品質上的缺陷。  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章