ibatis 開發指南

ibatis 開發指南

相對HibernateApache 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 Developer’s Guide Version 1.0

September 2, 2004 So many open source projects. Why not Open your Documents?

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">

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 POJOPlain 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

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>四個節點,我們分別定義了針對TUser 對象的增刪改查操作。在這四個節點中,我們指定了對應的SQL 語句,以update節點爲例:

……

<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 來執行此節點所定

義的操作,如:

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中的參數進行填充後提交數據庫執行。

此例對應的應用級代碼如下,其中演示了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系統初始化完畢,開始執行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();

}

其中,SqlMapClientibatis運作的核心,所有操作均通過SqlMapClient實例完成。

可以看出,對於應用層而言,程序員面對的是傳統意義上的數據對象,而非JDBC中煩雜的ResultSet,這使得上層邏輯開發人員的工作量大大減輕,同時代碼更加清晰簡潔。數據庫操作在映射文件中加以定義,從而將數據存儲邏輯從上層邏輯代碼中獨立出來。而底層數據操作的SQL可配置化,使得我們可以控制最終的數據操作方式,通過SQL的優化獲得最佳的數據庫執行效能,這在依賴SQL自動生成的“全自動”ORM機制中是所難以實現的。

ibatis配置

 

結合上面示例中的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" 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 節點

參數 描述

cacheModelsEnabled 是否啓用SqlMapClient上的緩存機制。

建議設爲"true"

enhancementEnabled 是否針對POJO啓用字節碼增強機制以提升getter/setter的調用效能,避免使用JavaReflect所帶來的性能開銷。同時,這也爲Lazy Loading帶來了極大的性能提升。

建議設爲"true"errorTracingEnabled 是否啓用錯誤日誌,在開發期間建議設爲"true"以方便調試lazyLoadingEnabled 是否啓用延遲加載機制,建議設爲"true"

maxRequests 最大併發請求數(Statement併發數)

maxTransactions 最大併發事務數

maxSessions 最大Session 數。即當前最大允許的併發

SqlMapClient數。

maxSessions設定必須介於

maxTransactionsmaxRequests之間,即

maxTransactions<maxSessions=<

maxRequests

useStatementNamespaces 是否使用Statement命名空間。

這裏的命名空間指的是映射文件中,sqlMap節點

namespace屬性,如在上例中針對t_user

表的映射文件sqlMap節點:

<sqlMap namespace="User">

這裏,指定了此sqlMap節點下定義的操作均從

屬於"User"命名空間。

useStatementNamespaces="true"的情

況下,Statement調用需追加命名空間,如:

sqlMap.update("User.updateUser",user);

否則直接通過Statement名稱調用即可,如:

sqlMap.update("updateUser",user);

但請注意此時需要保證所有映射文件中,

Statement定義無重名。

 transactionManager節點

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

SIMPLEibatis內置的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.MaximumActiveConnections

數據庫連接池可維持的最大容量。

Pool.MaximumIdleConnections

數據庫連接池中允許的掛起(idle)連接數。

以上子節點適用於SIMPLE DBCP 模式,分別針對SIMPLE DBCP 模式的

DataSource私有配置節點如下:

SIMPLE:

參數 描述

Pool.MaximumCheckoutTime

數據庫聯接池中,連接被某個任務所允許佔用的最大時間,如果超過這個時間限定,連接將被強制收回。(毫秒)

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中的配置相對簡單,下面是分別使用JDBCJTA事務管理的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>

IBATIS Developer’s Guide Version 1.0

September 2, 2004 So many open source projects. Why not Open your Documents?

</transactionManager>

 sqlMap節點

sqlMap 節點指定了映射文件的位置,配置中可出現多個sqlMap 節點,以指定項目內所包含的所有映射文件。

ibatis基礎語義

XmlSqlMapClientBuilder

XmlSqlMapClientBuilderibatis 2.0之後版本新引入的組件,用以替代1.x版本中的XmlSqlMapBuilder。其作用是根據配置文件創建SqlMapClient實例。

SqlMapClient

SqlMapClientibatis的核心組件,提供數據操作的基礎平臺。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();

IBATIS Developer’s Guide Version 1.0

September 2, 2004 So many open source projects. Why not Open your Documents?

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時,默認爲auto_commit模式

int rows = sqlMap.insert (“insertProduct”, product);

6:查詢指定範圍內的數據

sqlMap.startTransaction();

List list = sqlMap.queryForList (“getProductList”, null, 0, 40);

sqlMap.commitTransaction();

7: 結合RowHandler進行查詢(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: 分頁查詢 (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 映射

相對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>

IBATIS Developer’s Guide Version 1.0

September 2, 2004 So many open source projects. Why not Open your Documents?

</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主要有下面幾個配置點:

l flushInterval 

設定緩存有效期,如果超過此設定值,則將此CacheModel的緩存清空。

l size

CacheModel中最大容納的數據對象數量。

l flushOnExecute

指定執行特定Statement時,將緩存清空。如updateUser操作將更新數據庫中的用戶信息,這將導致緩存中的數據對象與數據庫中的實際數據發生偏差,因此必須將緩存清空以避免髒數據的出現。

關於Cache的深入探討,請參見“高級特性”中的相關章節。

Statement配置:

IBATIS Developer’s Guide Version 1.0

September 2, 2004 So many open source projects. Why not Open your Documents?

Statement配置包含了數個與SQL Statement相關的節點,分別爲:

u statement

u insert

u delete

u update

u select

u procedure

其中,statement最爲通用,它可以替代其餘的所有節點。除statement之外的節點各自對應了SQL中的同名操作(procedure對應存儲過程)。

使用statement 定義所有操作固然可以達成目標,但缺乏直觀性,建議在實際開發中根據操作目的,各自選用對應的節點名加以申明。一方面,使得配置文件更加直觀,另一方面,也可藉助DTD對節點申明進行更有針對性的檢查,以避免配置上的失誤。

各種類型的Statement 配置節點的參數類型基本一致,區別在於數量不同。如

insertupdatedelete節點無需返回數據類型定義(總是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:

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>

其中以“[]”包圍的部分爲可能出現的配置欄目。

參數 描述

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 對象的getNamegetSex getId 方法獲得相應的參數值,並將其作爲SQL 的參數。

如果parameterClass 中設定的是jdk 的中的簡單對象類型,如StringIntegeribatis會直接將其作爲SQL中的參數值。

我們也可以將包含了參數數據的Map對象傳遞給Statement,如:

<update id="updateUser"

parameterClass="java.util.Map">

UPDATE t_user

SET

name=#name#,

sex=#sex#

WHERE id = #id#

</update>

這裏傳入的參數就是一個Map對象,ibatis將以key name”、”sex”、”id”從中提取對應的參數值。

同樣的原理,我們也可以在resultMap中設定返回類型爲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>

返回的結果將以各字段名爲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會調用setNamesetSex 方法將Select語句返回的數據賦予相應的POJO 實例。

有些時候,數據庫表中的字段名過於晦澀,而爲了使得代碼更易於理解,我們希望字段映射到POJO時,採用比較易讀的屬性名, 此時,我們可以通過Selectas 字句對字段名進行轉義,如(假設我們的書庫中對應用戶名的字段爲xingming,對應性別的字段爲xingbie):

select

xingming as name,

xingbie as sex

from t_user

where id = #id#

ibatis 會根據轉義後的字段名進行屬性映射(即調用POJO setName 方法而不是setXingming方法)。

parameterMapresultMap實現了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>

ParameternullValue指定了如果參數爲空(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>

一般而言,對於insertupdatedeleteselect語句,優先採用parameterClass

resultClass

parameterMap 使用較少,而resultMap 則大多用於嵌套查詢以及存儲過程的處理,之所以這樣,原因是由於存儲過程相對而言比較封閉(很多情況下需要調用現有的存儲過程,其參數命名和返回的數據字段命名往往不符合Java編程中的命名習慣,並且由於我們難以通過Select SQLas子句進行字段名轉義,無法使其自動與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,

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>

對應代碼:

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+1Select問題。

注意上面示例運行過程中的日誌輸出:

……

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 表中有十萬條記錄,那麼這樣的操作將需要100000Select語句反覆執行才能獲得結果,無疑,隨着記錄的增長,這樣的開銷將無法承受。

之所以在這裏提及這個問題,目的在於引起讀者的注意,在系統設計中根據具體情況,採用一些規避手段(如使用存儲過程集中處理大批量關聯數據),從而避免因爲這個問題而引起產品品質上的缺陷。

一對一關聯

一對一關聯是一對多關聯的一種特例。這種情況下,如果採用上面的示例將導致1+1SQL的執行。

對於這種情況,我們可以採用一次Select兩張表的方式,避免這樣的性能開銷(假設上面示例中,每個User 只有一個對應的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

*

from t_user,t_address

where t_user.id=t_address.user_id

]]>

</select>

與此同時,應該保證User 類中包含addresszipCode兩個String型屬性。

IBATIS Developer’s Guide Version 1.0

September 2, 2004 So many open source projects. Why not Open your Documents?

延遲加載

在運行上面的例子時,通過觀察期間的日誌輸出順序我們可以發現,在我們執行sqlMap.queryForList("User.getUsers", "")時,實際上ibatis只向數據庫發送了一條select id, name, sex from t_user SQL。而用於獲取Address記錄的SQL,只有在我們真正訪問address對象時,纔開始執行。

這也就是所謂的延遲加載(Lazy Loading)機制。即當真正需要數據的時候,才加載數據。延遲加載機制能爲我們的系統性能帶來極大的提升。

試想,如果我們只需要獲取用戶名稱和性別數據,在沒有延遲加載特性的情況下,ibatis會一次將所有數據都從數據庫取回,包括用戶信息及其相關的地址數據,而此時,關於地址數據的讀取操作沒有意義,也就是說,我們白白在地址數據的查詢讀取上浪費了大量的系統資源。延遲加載爲我們妥善的處理了性能與編碼上的平衡(如果沒有延遲加載,我們爲了避免無謂的性能開銷,只能專門爲此再增加一個不讀取地址信息的用戶記錄檢索模塊,無疑增加了編碼上的工作量)。

回憶之前“ibatis配置”中的內容:

<settings 

……

enhancementEnabled="true"

lazyLoadingEnabled="true"

……

/>

Settings 節點有兩個與延遲加載相關的屬性lazyLoadingEnabled enhancementEnabled,其中lazyLoadingEnabled設定了系統是否使用延遲加載機制,enhancementEnabled設定是否啓用字節碼強化機制(通過字節碼強化機制可以爲Lazy Loading帶來性能方面的改進。

爲了使用延遲加載所帶來的性能優勢,這兩項都建議設爲"true"

動態映射

在複雜查詢過程中,我們常常需要根據用戶的選擇決定查詢條件,這裏發生變化的並不只是SQL 中的參數,包括Select 語句中所包括的字段和限定條件,都可能發生變化。典型情況,如在一個複雜的組合查詢頁面,我們必須根據用戶的選擇和輸入決定查詢的條件組合。

一個典型的頁面如下:

對於這個組合查詢頁面,根據用戶選擇填寫的內容,我們應爲其生成不同的查詢語句。

如用戶沒有填寫任何信息即提交查詢請求,我們應該返回所有記錄:

Select * from t_user

如用戶只在頁面上填寫了姓名“Erica”,我們應該生成類似:

Select * from t_user where name like '%Erica% 

SQL查詢語句。

如用戶只在頁面上填寫了地址“Beijing”,我們應該生成類似:

Select * from t_user where address like '%Beijing%”;

SQL

而如果用戶同時填寫了姓名和地址(”Erica&Beijing’),則我們應生成類似:

Select * from t_user where name like '%Erica%’ and address like '%Beijing%”

SQL查詢語句。

對於ibatis 這樣需要預先指定SQL 語句的ORM 實現而言,傳統的做法無非通過if-else 語句對輸入參數加以判定,然後針對用戶選擇調用不同的statement 定義。對於上面這種簡單的情況(兩種查詢條件的排列組合,共種情況)而言,statement 的重複定義工作已經讓人不厭其煩,而對於動輒擁有七八個查詢條件,乃至十幾個查詢條件的排列組合而言,瑣碎反覆的statement定義實在讓人不堪承受。

考慮到這個問題,ibatis引入了動態映射機制,即在statement定義中,根據不同的查詢參數,設定對應的SQL語句。

還是以上面的示例爲例:

<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 節點,我們定義了一個動態的WHERE 子句。此WHERE 子句中將可能包含兩個針對name address 字段的判斷條件。而這兩個字段是否加入檢索取決於用戶所提供的查詢條件(字段是否爲空[isNotEmpty])。

對於一個典型的Web程序而言,我們通過HttpServletRequest獲得表單中的字段名並將其設入查詢參數,如:

user.setName(request.getParameter("name"));

user.setAddress(request.getParameter("address"));

sqlMap.queryForList("User.getUsers", user);

在執行queryForList("User.getUsers", user)時,ibatis 即根據配置文件中設定的SQL動態生成規則,創建相應的SQL語句。

上面的示例中,我們通過判定節點isNotEmpty,指定了關於name address 屬性的動態規則:

<isNotEmpty prepend="AND" property="name">

(name like #name#)

</isNotEmpty>

這個節點對應的語義是,如果參數類的"name"屬性非空(isNotEmpty,即非空字符串””),則在生成的SQL Where字句中包括判定條件(name like #name#),其中#name#將以參數類的name屬性值填充。

Address屬性的判定生成與name屬性完全相同,這裏就不再贅述。

這樣,我們通過在statement 定義中引入dynamic 節點,很簡單的實現了SQL 判定子句的動態生成,對於複雜的組合查詢而言,這將帶來極大的便利。

判定節點的定義可以非常靈活,我們甚至可以使用嵌套的判定節點來實現複雜的動態映射,如:

<isNotEmpty prepend="AND" property="name">

( name=#name#

<isNotEmpty prepend="AND" property="address">

address=#address#

</isNotEmpty>

)

</isNotEmpty>

這段定義規定,只有用戶提供了姓名信息時,才能結合地址數據進行查詢(如果只提供地址數據,而將姓名信息忽略,將依然被視爲全檢索)。

Dynamic節點和判定節點中的prepend屬性,指明瞭本節點中定義的SQL子句在主體SQL中出現時的前綴。

如:

<dynamic prepend="WHERE">

<isNotEmpty prepend="AND" property="name">

(name like #name#)

</isNotEmpty>

<isNotEmpty prepend="AND" property="address">

(address like #address#)

</isNotEmpty>

</dynamic>

假設"name"屬性的值爲“Erica, "address"屬性的值爲“Beijing”,則會生成類似下面的SQL子句(實際運行期將生成帶佔位符的PreparedStatement,之後再爲其填充數據):

WHERE (name like 'Beijing’) AND (address like 'Beijing’)

其中WHERE 之後的語句是在dynamic 節點中所定義,因此以dynamic 節點的prepend設置("WHERE")作爲前綴,而其中的”AND”,實際上是address屬性所對應的isNotEmpty節點的prepend設定,它引領了對應節點中定義的SQL子句。至於name屬性對應的isNotEmpty節點,由於ibatis會自動判定是否需要追加prepend前綴,這裏(name like #name#)WHERE 子句中的第一個條件子句,無需AND 前綴,所以自動省略。

判定節點並非僅限於isNotEmptyibatis中提供了豐富的判定定義功能。

判定節點分兩類:

Ø 一元判定

一元判定是針對屬性值本身的判定,如屬性是否爲NULL,是否爲空值等。

上面示例中isNotEmpty就是典型的一元判定。

一元判定節點有:

節點名 描述

<isPropertyAvailable> 參數類中是否提供了此屬性

<isNotPropertyAvailable> <isPropertyAvailable>相反

<isNull> 屬性值是否爲NULL

<isNotNull> <isNull>相反

<isEmpty> 如果屬性爲Collection或者String,其size是否<1,如果非以上兩種類型,則通過

String.valueOf(屬性值)

獲得其String類型的值後,判斷其size是否<1<isNotEmpty> <isEmpty>相反。

Ø 二元判定

二元判定有兩個判定參數,一是屬性名,而是判定值,如

<isGreaterThan prepend="AND" property="age"

compareValue="18">

(age=#age#)

</isGreaterThan>

其中,property="age"指定了屬性名”age”,compareValue=18”指明瞭判定值爲”18”。

上面判定節點isGreaterThan 對應的語義是:如果age 屬性大於18(compareValue),則在SQL中加入(age=#age#)條件。

二元判定節點有:

節點名 屬性值與compareValues的關係

<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?

事務管理

基於JDBC的事務管理機制

ibatis提供了自動化的JDBC事務管理機制。

對於傳統JDBC Connection 而言,我們獲取Connection 實例之後,需要調用Connection.setAutoCommit設定事務提交模式。

AutoCommittrue的情況下,JDBC會對我們的操作進行自動提交,此時,每個JDBC操作都是一個獨立的任務。

爲了實現整體事務的原子性,我們需要將AutoCommit 設爲false,並結合Connection.commit/rollback方法進行事務的提交/回滾操作。

ibatis 的所謂“自動化的事務提交機制”,即ibatis 會根據當前的調用環境,自動判斷操作是否需要自動提交。

如果代碼沒有顯式的調用SqlMapClient.startTransaction()方法,則ibatis

會將當前的數據庫操作視爲自動提交模式(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);

在執行sqlMap.update的時候,ibatis會自動判定當前的運行環境,這裏update操作並沒有相對應的事務範圍(startTransactionendTransaction代碼塊),於是ibatis 將其作爲一個單獨的事務,並自動提交。對於上面的代碼,update 執行了兩次,與其相對應,事務也提交了兩次(即每個update操作爲一個單獨的事務)。

不過,值得注意的是,這裏的所謂“自動判定”,可能有些誤導,ibatis 並沒有去檢查當前是否已經有事務開啓,從而判斷當前數據庫連接是否設定爲自動提交。

實際上,在執行update語句時,sqlMap會檢查當前的Session是否已經關聯了某個數據庫連接,如果沒有,則取一個數據庫連接,將其AutoCommit屬性設爲true,然後執行update 操作,執行完之後又將這個連接釋放。這樣,上面兩次update 操作實際上先後獲取了兩個數據庫連接,而不是我們通常所認爲的兩次update 操作都基於同一個JDBC Connection。這點在開發時需特別注意。

對於多條SQL 組合而成的一個JDBC 事務操作而言,必須使用startTransactioncommitendTransaction操作以實現整體事務的原子性。

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 或者user2update操作失敗,整個事務就會在endTransaction時回滾,從而保證了兩次update操作的原子性。

基於JTA的事務管理機制JTA提供了跨數據庫連接(或其他JTA資源)的事務管理能力。這一點是與JDBCTransaction最大的差異。

JDBC事務由Connnection管理,也就是說,事務管理實際上是在JDBC Connection中實現。事務週期限於Connection的生命週期。同樣,對於基於JDBCibatis事務管理機制而言,事務管理在SqlMapClient所依託的JDBC Connection中實現,事務週期限於SqlMapClient 的生命週期。

JTA事務管理則由 JTA容器實現,JTA容器對當前加入事務的衆多Connection進行調度,實現其事務性要求。JTA的事務週期可橫跨多個JDBC Connection生命週期。

同樣,對於基於JTA事務的ibatis而言,JTA事務橫跨可橫跨多個SqlMapClient

下面這幅圖形象的說明了這個問題:

IBATIS Developer’s Guide Version 1.0

September 2, 2004 So many open source projects. Why not Open your Documents?

爲了在ibatis中使用JTA事務管理,我們需要在配置文件中加以設定:

<transactionManager type="JTA">

……

</transactionManager>

在實際開發中,我們可能需要面對分佈式事務的處理,如系統範圍內包含了多個數據庫,也許還引入了JMS 上的事務管理(這在EAI 系統實現中非常常見)。我們就需要引入JTA以實現系統範圍內的全局事務,如下面示例中,我們同時將user 對象更新到兩個不同的數據庫:

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();

}

上面的代碼中,兩個針對不同數據庫的SqlMapClient 實例,在同一個JTA 事務中對user 對象所對應的數據庫記錄進行更新。外層的sqlMap1 啓動了一個全局事務,此事務將涵蓋本線程內commitTransaction 之前的所有數據庫操作。只要其間發生了IBATIS Developer’s

異常,則整個事務都將被回滾。

外部事務管理

基於JTA的事務管理還有另外一個特殊情況,就是利用外部事務管理機制。

對於外部事務管理,我們需要在配置文件中進行如下設定:

<transactionManager type="EXTERNAL">

……

</transactionManager>

下面是一個外部事務管理的典型示例:

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();

此時,我們藉助JTA UserTransaction實例啓動了一個全局事務。之後的ibatis操作(sqlMap1.update sqlMap2.update)全部被包含在此全局事務之中,當UserTransaction提交的時候,ibatis操作被包含在事務中提交,反之,如果UserTransaction回滾,那麼其間的ibatis操作也將作爲事務的一部分被回滾。這樣,我們就實現了ibatis外部的事務控制。

另一種外部事務管理方式是藉助EJB 容器,通過EJB 的部署配置,我們可以指定EJB方法的事務性下面是一個Session BeandoUpdate方法,它的事務屬性被申明爲“Required”,EJB容器將自動維護此方法執行過程中的事務:

/**

* @ejb.interface-method

* view-type="remote"

*

* @ejb.transaction type = "Required"

**/

public void doUpdate(){

//EJB環境中,通過部署配置即可實現事務申明,而無需顯式調用事務……

sqlMap1 = xmlBuilder.buildSqlMap(db1Config);

sqlMap2 = xmlBuilder.buildSqlMap(db2Config);

sqlMap1.update("User.updateUser", user);

sqlMap2.update("User.updateUser", user);

……

}//方法結束時,如果沒有異常發生,則事務由EJB容器自動提交。

上面的示例中,ibatis數據操作的事務管理將全部委託給EJB容器管理,由EJB容器控制其事務調度。

在上面JTA事務管理的例子中,爲了保持清晰,我們省略了startTransactioncommitTransaction endTransaction 的編寫,在這種情況下,調用ibatis的事務管理方法並非必須,不過在實際開發時,請酌情添加startTransactioncommitTransaction endTransaction 語句,這樣可以獲得更好的性能(如果省略了startTransactioncommitTransactionendTransaction 語句,ibatis將爲每個數據操作獲取一個數據連接,就算引入了數據庫連接池機制,這樣的無謂開銷也應儘量避免,具體請參見JDBC事務管理中的描述),並保持代碼風格的統一。

Cache

在特定硬件基礎上(同時假設系統不存在設計上的缺漏和糟糕低效的SQL 語句)Cache往往是提升系統性能的最關鍵因素)。

相對Hibernate 等封裝較爲嚴密的ORM 實現而言(因爲對數據對象的操作實現了較爲嚴密的封裝,可以保證其作用範圍內的緩存同步,而ibatis 提供的是半封閉的封裝實現,因此對緩存的操作難以做到完全的自動化同步)。

ibatis 的緩存機制使用必須特別謹慎。特別是flushOnExecute 的設定(見“ibatis配置”一節中的相關內容),需要考慮到所有可能引起實際數據與緩存數據不符的操作。如本模塊中其他Statement對數據的更新,其他模塊對數據的更新,甚至第三方系統對數據的更新。否則,髒數據的出現將爲系統的正常運行造成極大隱患。

如果不能完全確定數據更新操作的波及範圍,建議避免Cache的盲目使用。

結合cacheModel來看:

<cacheModel

id="product-cache"

type ="LRU"

readOnly="true"

serialize="false">

</cacheModel>

可以看到,Cache有如下幾個比較重要的屬性:

Ø readOnly

Ø serialize

Ø type

readOnly

readOnly值的是緩存中的數據對象是否只讀。這裏的只讀並不是意味着數據對象一旦放入緩存中就無法再對數據進行修改。而是當數據對象發生變化的時候,如數據對象的某個屬性發生了變化,則此數據對象就將被從緩存中廢除,下次需要重新從數據庫讀取數據,構造新的數據對象。

readOnly="false"則意味着緩存中的數據對象可更新,如user 對象的name屬性發生改變。

只讀Cache能提供更高的讀取性能,但一旦數據發生改變,則效率降低。系統設計時需根據系統的實際情況(數據發生更新的概率有多大)來決定Cache的讀寫策略。

serialize

如果需要全局的數據緩存,CacheModelserialize屬性必須被設爲true。否則數據緩存只對當前Session(可簡單理解爲當前線程)有效,局部緩存對系統的整體性能提升有限。

serialize="true"的情況下,如果有多個Session同時從Cache 中讀取某個數據對象,Cache 將爲每個Session返回一個對象的複本,也就是說,每個Session 將得到包含相同信息的不同對象實例。因而Session 可以對其從Cache 獲得的數據進行存取而無需擔心多線程併發情況下的同步衝突。

Cache Type:

hibernate類似,ibatis通過緩衝接口的插件式實現,提供了多種Cache的實現機制可供選擇:

1 MEMORY

2 LRU

3 FIFO

4 OSCACHE

MEMORY類型CacheWeakReferenceMEMORY 類型的Cache 實現,實際上是通過Java 對象引用進行。ibatis 中,其實現類爲com.ibatis.db.sqlmap.cache.memory.MemoryCacheControllerMemoryCacheController 內部,

使用一個HashMap來保存當前需要緩存的數據對象的引用。

這裏需要注意的是Java2中的三種對象引用關係:

a SoftReference

b WeakReference

c PhantomReference

傳統的Java 對象引用,如:

public void doSomeThing(){

User user = new User()

……

}

doSomeThing方法結束時,user 對象的引用丟失,其所佔的內存空間將由JVM在下次垃圾回收時收回。如果我們將user 對象的引用保存在一個全局的HashMap中,如:

Map map = new HashMap();

public void doSomeThing(){

User user = new User();

map.put("user",user);

}

此時,user 對象由於在map 中保存了引用,只要這個引用存在,那麼JVM 永遠也不會收回user 對象所佔用的內存。

這樣的內存管理機制相信諸位都已經耳熟能詳,在絕大多數情況下,這幾乎是一種完美的解決方案。但在某些情況下,卻有些不便。如對於這裏的Cache 而言,當user 對象使用之後,我們希望保留其引用以供下次需要的時候可以重複使用,但又不希望這個引用長期保存,如果每個對象的引用都長期保存下去的話,那隨着時間推移,有限的內存空間將立即被這些數據所消耗殆盡。最好的方式,莫過於有一種引用方式,可以在對象沒有被垃圾回收器回收之前,依然能夠訪問此對象,當垃圾回收器啓動時,如果此對象沒有被其他對象所使用,則按照常規對其進行回收。

SoftReferenceWeakReferencePhantomReference爲上面的思路提供了有力支持。

這三種類型的引用都屬於“非持續性引用”,也就是說,這種引用關係並非持續存在,它們所代表的引用的生命週期與JVM 的運行密切相關,而非與傳統意義上的引用一樣依賴

於編碼階段的預先規劃。

先看一個SoftReference的例子:

SoftReference ref;

public void doSomeThing(){

User user = new User();

ref = new SoftReference(user);

}

public void doAnotherThing(){

User user = (User)ref.get();//通過SoftReference獲得對象引用

System.out.println(user.getName());

}

假設我們先執行了doSomeThing 方法,產生了一個User 對象,併爲其創建了一個

SoftReference引用。

之後的某個時刻,我們調用了doAnotherThing方法,並通過SoftReference獲取User 

象的引用。

此時我們是否還能取得user 對象的引用?這要看JVM 的運行情況。對於SoftReference而言,只有當目前內存不足的情況下,JVM 在垃圾回收時纔會收回其包含的引用(JVM 並不是只有當內存不足時才啓動垃圾回收機制,何時進行垃圾回收取決於各版本JVM 的垃圾回收策略。如某這垃圾回收策略爲:當系統目前較爲空閒,且無效對象達到一定比率時啓動垃圾回收機制,此時的空餘內存倒可能還比較充裕)。這裏可能出現兩種情況,即:Ø JVM 目前還未出現過因內存不足所引起的垃圾回收,user 對象的引用可以通過SoftReferenceJVM Heap中收回。

Ø JVM 已經因爲內存不足啓動了垃圾回收機制,SoftReference所包含的user 對象的引用被JVM 所廢棄。此時ref.get方法將返回一個空引用(null),對於上面的代碼而言,也就意味着這裏可能拋出一個NullPointerException

WeakReferenceSoftReference在引用的維持性上來看更加微弱。無需等到內存不足的情況,只要JVM 啓動了垃圾回收機制,那麼WeakReference所對應的對象就將被JVM 回收。

也就是說,相對SoftReference而言,WeakReference JVM 回收的概率更大。

PhantomReference WeakReference 的引用維持性更弱。與WeakReference SoftReference不同,PhantomReference所引用的對象幾乎無法被回收重用。它指向的對象實際上已經被JVM 銷燬(finalize方法已經被執行),只是暫時還沒被垃圾回收器收回而已。PhantomReference主要用於輔助對象的銷燬過程,在實際應用層研發中,幾乎不會涉及。MEMORY類型的Cache正是藉助SoftReferenceWeakReference以及通常意義上的JavaReference實現了對象的緩存管理。

下面是一個典型的MEMORY類型Cache配置:

<cacheModel id="user_cache" type="MEMORY">

<flushInterval hours="24"/>

<flushOnExecute statement="updateUser"/>

<property name="reference-type" value="WEAK" />

</cacheModel>

其中flushInterval 指定了多長時間清除緩存,上例中指定每24 小時強行清空緩存區的所有內容。

reference-type屬性可以有以下幾種配置:

1. STRONG

即基於傳統的Java對象引用機制,除非對Cache顯式清空(如到了flushInterval設定的時間;執行了flushOnExecute所指定的方法;或代碼中對Cache執行了清除操作等),否則引用將被持續保留。

此類型的設定適用於緩存常用的數據對象,或者當前系統內存非常充裕的情況。

2. SOFT

基於SoftReference 的緩存實現,只有JVM 內存不足的時候,纔會對緩衝池中的數據對象進行回收。

此類型的設定適用於系統內存較爲充裕,且系統併發量比較穩定的情況。

3. WEAK

基於WeakReference 的緩存實現,當JVM 垃圾回收時,緩存中的數據對象將被JVM收回。

一般情況下,可以採用WEAKMEMORYCache配置。

LRUCache

Cache達到預先設定的最大容量時,ibatis會按照“最少使用”原則將使用頻率最少的對象從緩衝中清除。

<cacheModel id="userCache" type="LRU">

<flushInterval hours="24"/>

<flushOnExecute statement="updateUser"/>

<property name="size" value="1000" />

</cacheModel>

可配置的參數有:

u flushInterval

指定了多長時間清除緩存,上例中指定每24小時強行清空緩存區的所有內容。

u size

Cache的最大容量。

FIFOCache

先進先出型緩存,最先放入Cache中的數據將被最先廢除。可配置參數與LRU型相同:

<cacheModel id="userCache" type="FIFO">

<flushInterval hours="24"/>

<flushOnExecute statement="updateUser"/>

<property name="size" value="1000" />

</cacheModel>

OSCache

與上面幾種類型的Cache不同,OSCache來自第三方組織Opensymphony。可以通過以下網址獲得OSCache的最新版本(http://www.opensymphony.com/oscache/)。

在生產部署時,建議採用OSCacheOSCache 是得到了廣泛使用的開源Cache 實現(Hibernate 中也提供了對OSCache 的支持),它基於更加可靠高效的設計,更重要的是,最新版本的OSCache 已經支持Cache 集羣。如果系統需要部署在集羣中,或者需要部署在多機負載均衡模式的環境中以獲得性能上的優勢,那麼OSCache在這裏則是不二之選。

Ibatis中對於OSCache的配置相當簡單:

<cacheModel id="userCache" type="OSCACHE">

<flushInterval hours="24"/>

<flushOnExecute statement="updateUser"/>

<property name="size" value="1000" />

</cacheModel>

之所以配置簡單,原因在於,OSCache擁有自己的配置文件(oscache.propertiesJ

下面是一個典型的OSCache配置文件:

#是否使用內存作爲緩存空間

cache.memory=true

#緩存管理事件監聽器,通過這個監聽器可以獲知當前Cache 的運行情況cache.event.listeners=com.opensymphony.oscache.plugins.clustersupport.JMSBroa

dcastingListener

#如果使用磁盤緩存(cache.memory=false),則需要指定磁盤存儲接口實現#cache.persistence.class=com.opensymphony.oscache.plugins.diskpersistence.Disk

PersistenceListener

磁盤緩存所使用的文件存儲路徑

# cache.path=c:""myapp""cache

緩存調度算法,可選的有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

#內存緩存的最大容量

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 在初始化時會自動找到此

文件並根據其中的配置創建緩存實例。

IBATIS Developer’s Guide Version 1.0

September 2, 2004 So many open source projects. Why not Open your Documents?

ibatis in Spring

這裏我們重點探討Spring框架下的ibatis應用,特別是在容器事務管理模式下的ibatis

應用開發。

關於Spring Framework,請參見筆者另一篇文檔:

Spring 開發指南》http://www.xiaxin.net/Spring_Dev_Guide.rar

針對ibatisSpring配置文件如下:

Ibatis-Context.xml:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"

"http://www.springframework.org/dtd/spring-beans.dtd">

<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節點

sqlMapClient節點實際上配置了一個sqlMapClient的創建工廠類。

configLocation屬性配置了ibatis映射文件的名稱。

2.    transactionManager節點

transactionManager採用了Spring中的DataSourceTransactionManager

3.    userDAO節點

IBATIS Developer’s Guide Version 1.0

September 2, 2004 So many open source projects. Why not Open your Documents?

對應的,UserDAO需要配置兩個屬性,sqlMapClientDataSource

sqlMapClient將從指定的DataSource中獲取數據庫連接。

本例中Ibatis映射文件非常簡單:

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版本,對應支持類是SqlMapDaoSupport)是Spring中面向ibatis的輔助類,它負責調度DataSourceSqlMapClientTemplate(對應ibatis 1.x版本是SqlMapTemplate)完成ibatis操作,而DAO則通過對此類進行擴展獲得上述功能。上面配置文件中針對UserDAO的屬性設置部分,其中的屬性也是繼承自於這個基類。

SqlMapClientTemplate對傳統SqlMapClient調用模式進行了封裝,簡化了上層訪問代碼。

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;

}

}

測試代碼:

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);

對比前面ibatis 程序代碼,我們可以發現UserDAO.java 變得異常簡潔,這也正是Spring Framework爲我們帶來的巨大幫助。

Spring以及其他IOC 框架對傳統的應用框架進行了顛覆性的革新,也許這樣的評價有點過於煽情,但是這確是筆者第一次跑通Spring HelloWorld 時的切身感受。有興趣的讀者可以研究一下Spring frameworkenjoy it

發佈了10 篇原創文章 · 獲贊 12 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章