在您撰寫好*.hbm.xml映射文件之後,您可以使用 net.sf.hibernate.tool.hbm2ddl.SchemaExportTask來自動建立資料庫表格,這邊所使用的方式是結合Ant進行自動化建構,首先我們假設將使用以下的User.hbm.xml:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-
"http:>
<hibernate-mapping>
<class name="onlyfun.caterpillar.User" table="USER">
<id name="id" type="string" unsaved-value="null">
<column name="user_id" sql-type="char(32)"/>
<generator class="uuid.hex"/>
</id>
<property name="name" type="string" not-null="true">
<column name="name" length="16" not-null="true"/>
</property>
<property name="sex" type="char" />
<property name="age" type="int"/>
</class>
</hibernate-mapping>
在這個映射文件中,<column/>標籤用於指定建立表格時的一些資訊,例如映射的表格欄位名稱,或是sql-type或 length等屬性,如果不指定這些資訊時,SchemaExportTask將自動使用Hibernate的類型至SQL類型等資訊來建立表格;sql -type用於指定表格欄位型態,not-null表示欄位不能為null,length則用於指定表格文字欄位長度,這些屬性的說明,都可以在 Hibernate參考手冊的表15.1找到。
下面的build.xml用於Ant自動化建構時,生成資料庫表格之用:
<project name="Hibernate" default="schema" basedir=".">
<property name="source.root" value="src"/>
<property name="class.root" value="classes"/>
<property name="lib.dir" value="lib"/>
<property name="data.dir" value="data"/>
<path id="project.class.path">
<!-- Include our own classes, of course -->
<pathelement location="${class.root}" />
<!-- Include jars in the project library directory -->
<fileset dir="${lib.dir}">
<include name="*.jar"/>
</fileset>
<pathelement path ="${classpath}"/>
</path>
<target name="schema" description="Generate DB schema from the O/R mapping files">
<!-- Teach Ant how to use Hibernate's schema generation tool -->
<taskdef name="schemaexport"
classname="net.sf.hibernate.tool.hbm2ddl.SchemaExportTask"
classpathref="project.class.path"/>
<schemaexport properties="${source.root}/hibernate.properties"
quiet="no" text="no" drop="no" delimiter=";">
<fileset dir="${source.root}">
<include name="**/*.hbm.xml"/>
</fileset>
</schemaexport>
</target>
</project>
<taskdef/>標籤定義一個新的任務schemaexport,相關的屬性設定是根據參考手冊的建議設定的,我們在這邊使用 hibernate.properties來告訴SchemaExportTask相關的JDBC資訊,quiet、text等屬性的定義,可以看參考手冊的表15.2。
這個Ant建構檔案,會找尋src目錄下包括子目錄中有的*.hbm.xml,並自動根據映射資訊建立表格,我們還必須提供hibernate.properties(置於src下)來告知JDBC連接的相關訊息:
hibernate.dialect=net.sf.hibernate.dialect.MySQLDialect
hibernate.connection.driver_class=com.mysql.jdbc.Driver
hibernate.connection.url=jdbc:mysql:hibernate.connection.username=caterpillar
hibernate.connection.password=123456
這邊使用的是MySQL,請實際根據您所使用的資料庫設定dialect、驅動程式等資訊,在開始運行Ant使用SchemaExportTask進行自動表格建立之前,您要先建立資料庫,這邊的例子則是在MySQL中先建立HibernateTest:
mysql> create database HibernateTest;
Query OK, 1 row affected (0.03 sec)
接著就可以運行Ant了,執行結果如下:
ant
Buildfile: build.xml
schema:
[schemaexport] log4j:WARN No appenders could be found for logger (net.sf.hiberna
te.cfg.Environment).
[schemaexport] log4j:WARN Please initialize the log4j system properly.
[schemaexport] drop table if exists USER;
[schemaexport] create table USER (
[schemaexport] user_id char(32) not null,
[schemaexport] name varchar(16) not null,
[schemaexport] sex char(1),
[schemaexport] age integer,
[schemaexport] primary key (user_id)
[schemaexport] );
BUILD SUCCESSFUL
Total time: 5 seconds
運行的過程中,我們可以看到建立表格的SQL語句,而自動建立好的資料庫表格資訊如下:
mysql> DESCRIBE user;
+---------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+-------+
| user_id | varchar(32) | | PRI | | |
| name | varchar(16) | | | | |
| sex | char(1) | YES | | NULL | |
| age | int(11) | YES | | NULL | |
+---------+-------------+------+-----+---------+-------+
4 rows in set (0.04 sec)
更多有關SchemaExportTask的資訊,可以看看參考手冊的第15章工具箱指南的部份。
posted @
2009-04-11 11:37 lanxin1020 閱讀(262) |
評論 (0) |
編輯 收藏
延遲加載:
延遲加載機制是為了避免一些無謂的性能開銷而提出來的,所謂延遲加載就是當在真正需要數據的時候,才真正執行數據加載操作。在Hibernate中提供了對實體對象的延遲加載以及對集合的延遲加載,另外在Hibernate3中還提供了對屬性的延遲加載。下面我們就分別介紹這些種類的延遲加載的細節。
A、實體對象的延遲加載:
如果想對實體對象使用延遲加載,必須要在實體的映射配置文件中進行相應的配置,如下所示:
<hibernate-mapping>
<class name=”com.neusoft.entity.User” table=”user”
lazy=”true”>
……
</class>
</hibernate-mapping>
通過將class的lazy屬性設置為true,來開啟實體的延遲加載特性。如果我們運行下面的代碼:
User user=(User)session.load(User.class,”1”);(1)
System.out.println(user.getName());(2)
當運行到(1)處時,Hibernate并沒有發起對數據的查詢,如果我們此時通過一些調試工具(比如JBuilder2005的Debug工具),觀察此時user對象的內存快照,我們會驚奇的發現,此時返回的可能是User$EnhancerByCGLIB$$bede8986類型的對象,而且其屬性為null,這是怎么回事?還記得前面我曾講過session.load()方法,會返回實體對象的代理類對象,這里所返回的對象類型就是User對象的代理類對象。在Hibernate中通過使用CGLIB,來實現動態構造一個目標對象的代理類對象,并且在代理類對象中包含目標對象的所有屬性和方法,而且所有屬性均被賦值為null。通過調試器顯示的內存快照,我們可以看出此時真正的User對象,是包含在代理對象的CGLIB$CALBACK_0.target屬性中,當代碼運行到(2)處時,此時調用user.getName()方法,這時通過CGLIB賦予的回調機制,實際上調用CGLIB$CALBACK_0.getName()方法,當調用該方法時,Hibernate會首先檢查CGLIB$CALBACK_0.target屬性是否為null,如果不為空,則調用目標對象的getName方法,如果為空,則會發起數據庫查詢,生成類似這樣的SQL語句:select * from user where id=’1’;來查詢數據,并構造目標對象,并且將它賦值到CGLIB$CALBACK_0.target屬性中。
這樣,通過一個中間代理對象,Hibernate實現了實體的延遲加載,只有當用戶真正發起獲得實體對象屬性的動作時,才真正會發起數據庫查詢操作。所以實體的延遲加載是用通過中間代理類完成的,所以只有session.load()方法才會利用實體延遲加載,因為只有session.load()方法才會返回實體類的代理類對象。
B、 集合類型的延遲加載:
在Hibernate的延遲加載機制中,針對集合類型的應用,意義是最為重大的,因為這有可能使性能得到大幅度的提高,為此Hibernate進行了大量的努力,其中包括對JDK Collection的獨立實現,我們在一對多關聯中,定義的用來容納關聯對象的Set集合,并不是java.util.Set類型或其子類型,而是net.sf.hibernate.collection.Set類型,通過使用自定義集合類的實現,Hibernate實現了集合類型的延遲加載。為了對集合類型使用延遲加載,我們必須如下配置我們的實體類的關于關聯的部分:
<hibernate-mapping>
<class name=”com.neusoft.entity.User” table=”user”>
…..
<set name=”addresses” table=”address” lazy=”true” inverse=”true”>
<key column=”user_id”/>
<one-to-many class=”com.neusoft.entity.Arrderss”/>
</set>
</class>
</hibernate-mapping>
通過將<set>元素的lazy屬性設置為true來開啟集合類型的延遲加載特性。我們看下面的代碼:
User user=(User)session.load(User.class,”1”);
Collection addset=user.getAddresses(); (1)
Iterator it=addset.iterator(); (2)
while(it.hasNext()){
Address address=(Address)it.next();
System.out.println(address.getAddress());
}
當程序執行到(1)處時,這時并不會發起對關聯數據的查詢來加載關聯數據,只有運行到(2)處時,真正的數據讀取操作才會開始,這時Hibernate會根據緩存中符合條件的數據索引,來查找符合條件的實體對象。
這里我們引入了一個全新的概念——數據索引,下面我們首先將接一下什么是數據索引。在Hibernate中對集合類型進行緩存時,是分兩部分進行緩存的,首先緩存集合中所有實體的id列表,然后緩存實體對象,這些實體對象的id列表,就是所謂的數據索引。當查找數據索引時,如果沒有找到對應的數據索引,這時就會一條select SQL的執行,獲得符合條件的數據,并構造實體對象集合和數據索引,然后返回實體對象的集合,并且將實體對象和數據索引納入Hibernate的緩存之中。另一方面,如果找到對應的數據索引,則從數據索引中取出id列表,然后根據id在緩存中查找對應的實體,如果找到就從緩存中返回,如果沒有找到,在發起select SQL查詢。在這里我們看出了另外一個問題,這個問題可能會對性能產生影響,這就是集合類型的緩存策略。如果我們如下配置集合類型:
<hibernate-mapping>
<class name=”com.neusoft.entity.User” table=”user”>
…..
<set name=”addresses” table=”address”
lazy=”true” inverse=”true”>
<cache usage=”read-only”/><key column=”user_id”/>
<one-to-many class=”com.neusoft.entity.Arrderss”/>
</set>
</class>
</hibernate-mapping>
這里我們應用了<cache usage=”read-only”/>配置,如果采用這種策略來配置集合類型,Hibernate將只會對數據索引進行緩存,而不會對集合中的實體對象進行緩存。如上配置我們運行下面的代碼:
User user=(User)session.load(User.class,”1”);
Collection addset=user.getAddresses();
Iterator it=addset.iterator();
while(it.hasNext()){
Address address=(Address)it.next();
System.out.println(address.getAddress());
}
System.out.println(“Second query……”);
User user2=(User)session.load(User.class,”1”);
Collection it2=user2.getAddresses();
while(it2.hasNext()){
Address address2=(Address)it2.next();
System.out.println(address2.getAddress());
}
運行這段代碼,會得到類似下面的輸出:
Select * from user where id=’1’;
Select * from address where user_id=’1’;
Tianjin
Dalian
Second query……
Select * from address where id=’1’;
Select * from address where id=’2’;
Tianjin
Dalian
我們看到,當第二次執行查詢時,執行了兩條對address表的查詢操作,為什么會這樣?這是因為當第一次加載實體后,根據集合類型緩存策略的配置,只對集合數據索引進行了緩存,而并沒有對集合中的實體對象進行緩存,所以在第二次再次加載實體時,Hibernate找到了對應實體的數據索引,但是根據數據索引,卻無法在緩存中找到對應的實體,所以Hibernate根據找到的數據索引發起了兩條select SQL的查詢操作,這里造成了對性能的浪費,怎樣才能避免這種情況呢?我們必須對集合類型中的實體也指定緩存策略,所以我們要如下對集合類型進行配置:
<hibernate-mapping>
<class name=”com.neusoft.entity.User” table=”user”>
…..
<set name=”addresses” table=”address”
lazy=”true” inverse=”true”>
<cache usage=”read-write”/><key column=”user_id”/>
<one-to-many class=”com.neusoft.entity.Arrderss”/>
</set>
</class>
</hibernate-mapping>
此時Hibernate會對集合類型中的實體也進行緩存,如果根據這個配置再次運行上面的代碼,將會得到類似如下的輸出:
Select * from user where id=’1’;
Select * from address where user_id=’1’;
Tianjin
Dalian
Second query……
Tianjin
Dalian
這時將不會再有根據數據索引進行查詢的SQL語句,因為此時可以直接從緩存中獲得集合類型中存放的實體對象。
C、 屬性延遲加載:
在Hibernate3中,引入了一種新的特性——屬性的延遲加載,這個機制又為獲取高性能查詢提供了有力的工具。在前面我們講大數據對象讀取時,在User對象中有一個resume字段,該字段是一個java.sql.Clob類型,包含了用戶的簡歷信息,當我們加載該對象時,我們不得不每一次都要加載這個字段,而不論我們是否真的需要它,而且這種大數據對象的讀取本身會帶來很大的性能開銷。在Hibernate2中,我們只有通過我們前面講過的面性能的粒度細分,來分解User類,來解決這個問題(請參照那一節的論述),但是在Hibernate3中,我們可以通過屬性延遲加載機制,來使我們獲得只有當我們真正需要操作這個字段時,才去讀取這個字段數據的能力,為此我們必須如下配置我們的實體類:
<hibernate-mapping>
<class name=”com.neusoft.entity.User” table=”user”>
……
<property name=”resume” type=”java.sql.Clob” column=”resume” lazy=”true”/> </class>
</hibernate-mapping>
通過對<property>元素的lazy屬性設置true來開啟屬性的延遲加載,在Hibernate3中為了實現屬性的延遲加載,使用了類增強器來對實體類的Class文件進行強化處理,通過增強器的增強,將CGLIB的回調機制邏輯,加入實體類,這里我們可以看出屬性的延遲加載,還是通過CGLIB來實現的。CGLIB是Apache的一個開源工程,這個類庫可以操縱java類的字節碼,根據字節碼來動態構造符合要求的類對象。根據上面的配置我們運行下面的代碼:
String sql=”from User user where user.name=’zx’ ”;
Query query=session.createQuery(sql); (1)
List list=query.list();
for(int i=0;i<list.size();i++){
User user=(User)list.get(i);
System.out.println(user.getName());
System.out.println(user.getResume()); (2)
}
當執行到(1)處時,會生成類似如下的SQL語句:
Select id,age,name from user where name=’zx’;
這時Hibernate會檢索User實體中所有非延遲加載屬性對應的字段數據,當執行到(2)處時,會生成類似如下的SQL語句:
Select resume from user where id=’1’;
這時會發起對resume字段數據真正的讀取操作。
posted @
2009-04-11 10:35 lanxin1020 閱讀(117) |
評論 (0) |
編輯 收藏
1.1.1. 基本的緩存原理
Hibernate緩存分為二級,第一級存放于session中稱為一級緩存,默認帶有且不能卸載。
第二級是由sessionFactory控制的進程級緩存。是全局共享的緩存,凡是會調用二級緩存的查詢方法 都
會從中受益。只有經正確的配置后二級緩存才會發揮作用。同時在進行條件查詢時必須使用相應的方法
才能從緩存中獲取數據。比如Query.iterate()方法、load、get方法等。必須注意的是session.find方
法永遠是從數據庫中獲取數據,不會從二級緩存中獲取數據,即便其中有其所需要的數據也是如此。
查詢時使用緩存的實現過程為:首先查詢一級緩存中是否具有需要的數據,如果沒有,查詢二級緩存,
如果二級緩存中也沒有,此時再執行查詢數據庫的工作。要注意的是:此3種方式的查詢速度是依次降低
的。
1.2. 存在的問題
1.2.1. 一級緩存的問題以及使用二級緩存的原因
因為Session的生命期往往很短,存在于Session內部的第一級最快緩存的生命期當然也很短,所以
第一級緩存的命中率是很低的。其對系統性能的改善也是很有限的。當然,這個Session內部緩存的主要
作用是保持Session內部數據狀態同步。并非是hibernate為了大幅提高系統性能所提供的。
為了提高使用hibernate的性能,除了常規的一些需要注意的方法比如:
使用延遲加載、迫切外連接、查詢過濾等以外,還需要配置hibernate的二級緩存。其對系統整體性能的
改善往往具有立竿見影的效果!
(經過自己以前作項目的經驗,一般會有3~4倍的性能提高)
1.2.2. N+1次查詢的問題
執行條件查詢時,iterate()方法具有著名的 “n+1”次查詢的問題,也就是說在第一次查詢時
iterate方法會執行滿足條件的查詢結果數再加一次(n+1)的查詢。但是此問題只存在于第一次查詢時
,在后面執行相同查詢時性能會得到極大的改善。此方法適合于查詢數據量較大的業務數據。
但是注意:當數據量特別大時(比如流水線數據等)需要針對此持久化對象配置其具體的緩存策略,比
如設置其存在于緩存中的最大記錄數、緩存存在的時間等參數,以避免系統將大量的數據同時裝載入內
存中引起內存資源的迅速耗盡,反而降低系統的性能?。?!
1.3. 使用hibernate二級緩存的其他注意事項:
1.3.1. 關于數據的有效性
另外,hibernate會自行維護二級緩存中的數據,以保證緩存中的數據和數據庫中的真實數據的一致性!
無論何時,當你調用save()、update()或 saveOrUpdate()方法傳遞一個對象時,或使用load()、 get()
、list()、iterate() 或scroll()方法獲得一個對象時, 該對象都將被加入到Session的內部緩存中。
當隨后flush()方法被調用時,對象的狀態會和數據庫取得同步。
也就是說刪除、更新、增加數據的時候,同時更新緩存。當然這也包括二級緩存!
只要是調用hibernate API執行數據庫相關的工作。hibernate都會為你自動保證 緩存數據的有效性??!
但是,如果你使用了JDBC繞過hibernate直接執行對數據庫的操作。此時,Hibernate不會/也不可能自行
感知到數據庫被進行的變化改動,也就不能再保證緩存中數據的有效性?。?
這也是所有的ORM產品共同具有的問題。幸運的是,Hibernate為我們暴露了Cache的清除方法,這給我們
提供了一個手動保證數據有效性的機會!!
一級緩存,二級緩存都有相應的清除方法。
其中二級緩存提供的清除方法為:
按對象class清空緩存
按對象class和對象的主鍵id清空緩存
清空對象的集合中的緩存數據等。
1.3.2. 適合使用的情況
并非所有的情況都適合于使用二級緩存,需要根據具體情況來決定。同時可以針對某一個持久化對象配
置其具體的緩存策略。
適合于使用二級緩存的情況:
1、數據不會被第三方修改;
一般情況下,會被hibernate以外修改的數據最好不要配置二級緩存,以免引起不一致的數據。但是如果
此數據因為性能的原因需要被緩存,同時又有可能被第3方比如SQL修改,也可以為其配置二級緩存。只
是此時需要在sql執行修改后手動調用cache的清除方法。以保證數據的一致性
2、數據大小在可接收范圍之內;
如果數據表數據量特別巨大,此時不適合于二級緩存。原因是緩存的數據量過大可能會引起內存資
源緊張,反而降低性能。
如果數據表數據量特別巨大,但是經常使用的往往只是較新的那部分數據。此時,也可為其配置二級緩
存。但是必須單獨配置其持久化類的緩存策略,比如最大緩存數、緩存過期時間等,將這些參數降低至
一個合理的范圍(太高會引起內存資源緊張,太低了緩存的意義不大)。
3、數據更新頻率低;
對于數據更新頻率過高的數據,頻繁同步緩存中數據的代價可能和 查詢緩存中的數據從中獲得的
好處相當,壞處益處相抵消。此時緩存的意義也不大。
4、非關鍵數據(不是財務數據等)
財務數據等是非常重要的數據,絕對不允許出現或使用無效的數據,所以此時為了安全起見最好不要
使用二級緩存。
因為此時 “正確性”的重要性遠遠大于 “高性能”的重要性。
2. 目前系統中使用hibernate緩存的建議
1.4. 目前情況
一般系統中有三種情況會繞開hibernate執行數據庫操作:
1、多個應用系統同時訪問一個數據庫
此種情況使用hibernate二級緩存會不可避免的造成數據不一致的問題,
此時要進行詳細的設計。比如在設計上避免對同一數據表的同時的寫入操作,
使用數據庫各種級別的鎖定機制等。
2、動態表相關
所謂“動態表”是指在系統運行時根據用戶的操作系統自動建立的數據表。
比如“自定義表單”等屬于用戶自定義擴展開發性質的功能模塊,因為此時數據表是運行時建立的,
所以不能進行hibernate的映射。因此對它的操作只能是繞開hibernate的直接數據庫JDBC操作。
如果此時動態表中的數據沒有設計緩存,就不存在數據不一致的問題。
posted @
2009-04-11 10:24 lanxin1020 閱讀(513) |
評論 (0) |
編輯 收藏
緩存是介于應用程序和物理數據源之間,其作用是為了降低應用程序對物理數據源訪問的頻次,從而提高了應用的運行性能。緩存內的數據是對物理數據源中的數據的復制,應用程序在運行時從緩存讀寫數據,在特定的時刻或事件會同步緩存和物理數據源的數據。
緩存的介質一般是內存,所以讀寫速度很快。但如果緩存中存放的數據量非常大時,也會用硬盤作為緩存介質。緩存的實現不僅僅要考慮存儲的介質,還要考慮到管理緩存的并發訪問和緩存數據的生命周期。
Hibernate的緩存包括Session的緩存和SessionFactory的緩存,其中SessionFactory的緩存又可以分為兩類:內置緩存和外置緩存。Session的緩存是內置的,不能被卸載,也被稱為Hibernate的第一級緩存。SessionFactory的內置緩存和Session的緩存在實現方式上比較相似,前者是SessionFactory對象的一些集合屬性包含的數據,后者是指Session的一些集合屬性包含的數據。SessionFactory的內置緩存中存放了映射元數據和預定義SQL語句,映射元數據是映射文件中數據的拷貝,而預定義SQL語句是在Hibernate初始化階段根據映射元數據推導出來,SessionFactory的內置緩存是只讀的,應用程序不能修改緩存中的映射元數據和預定義SQL語句,因此SessionFactory不需要進行內置緩存與映射文件的同步。SessionFactory的外置緩存是一個可配置的插件。在默認情況下,SessionFactory不會啟用這個插件。外置緩存的數據是數據庫數據的拷貝,外置緩存的介質可以是內存或者硬盤。SessionFactory的外置緩存也被稱為Hibernate的第二級緩存。
Hibernate的這兩級緩存都位于持久化層,存放的都是數據庫數據的拷貝,那么它們之間的區別是什么呢?為了理解二者的區別,需要深入理解持久化層的緩存的兩個特性:緩存的范圍和緩存的并發訪問策略。
持久化層的緩存的范圍
緩存的范圍決定了緩存的生命周期以及可以被誰訪問。緩存的范圍分為三類。
1 事務范圍:緩存只能被當前事務訪問。緩存的生命周期依賴于事務的生命周期,當事務結束時,緩存也就結束生命周期。在此范圍下,緩存的介質是內存。事務可以是數據庫事務或者應用事務,每個事務都有獨自的緩存,緩存內的數據通常采用相互關聯的的對象形式。
2 進程范圍:緩存被進程內的所有事務共享。這些事務有可能是并發訪問緩存,因此必須對緩存采取必要的事務隔離機制。緩存的生命周期依賴于進程的生命周期,進程結束時,緩存也就結束了生命周期。進程范圍的緩存可能會存放大量的數據,所以存放的介質可以是內存或硬盤。緩存內的數據既可以是相互關聯的對象形式也可以是對象的松散數據形式。松散的對象數據形式有點類似于對象的序列化數據,但是對象分解為松散的算法比對象序列化的算法要求更快。
3 集群范圍:在集群環境中,緩存被一個機器或者多個機器的進程共享。緩存中的數據被復制到集群環境中的每個進程節點,進程間通過遠程通信來保證緩存中的數據的一致性,緩存中的數據通常采用對象的松散數據形式。
對大多數應用來說,應該慎重地考慮是否需要使用集群范圍的緩存,因為訪問的速度不一定會比直接訪問數據庫數據的速度快多少。
持久化層可以提供多種范圍的緩存。如果在事務范圍的緩存中沒有查到相應的數據,還可以到進程范圍或集群范圍的緩存內查詢,如果還是沒有查到,那么只有到數據庫中查詢。事務范圍的緩存是持久化層的第一級緩存,通常它是必需的;進程范圍或集群范圍的緩存是持久化層的第二級緩存,通常是可選的。
持久化層的緩存的并發訪問策略
當多個并發的事務同時訪問持久化層的緩存的相同數據時,會引起并發問題,必須采用必要的事務隔離措施。
在進程范圍或集群范圍的緩存,即第二級緩存,會出現并發問題。因此可以設定以下四種類型的并發訪問策略,每一種策略對應一種事務隔離級別。
事務型:僅僅在受管理環境中適用。它提供了Repeatable Read事務隔離級別。對于經常被讀但很少修改的數據,可以采用這種隔離類型,因為它可以防止臟讀和不可重復讀這類的并發問題。
讀寫型:提供了Read Committed事務隔離級別。僅僅在非集群的環境中適用。對于經常被讀但很少修改的數據,可以采用這種隔離類型,因為它可以防止臟讀這類的并發問題。
非嚴格讀寫型:不保證緩存與數據庫中數據的一致性。如果存在兩個事務同時訪問緩存中相同數據的可能,必須為該數據配置一個很短的數據過期時間,從而盡量避免臟讀。對于極少被修改,并且允許偶爾臟讀的數據,可以采用這種并發訪問策略。
只讀型:對于從來不會修改的數據,如參考數據,可以使用這種并發訪問策略。
事務型并發訪問策略是事務隔離級別最高,只讀型的隔離級別最低。事務隔離級別越高,并發性能就越低。
posted @
2009-04-11 10:18 lanxin1020 閱讀(123) |
評論 (0) |
編輯 收藏
1.Hibernate 的初始化.
讀取Hibernate 的配置信息-〉創建Session Factory
1)創建Configeration類的實例。
它的構造方法:將配置信息(Hibernate config.xml)讀入到內存。
一個Configeration 實例代表Hibernate 所有Java類到Sql數據庫映射的集合。
2)創建SessionFactory實例
把Configeration 對象中的所有配置信息拷貝到SessionFactory的緩存中。
SessionFactory的實例代表一個數據庫存儲員源,創建后不再與Configeration 對象關聯。
緩存(cache):指Java對象的屬性(通常是一些集合類型的屬性--占用內存空間。
SessionFactory的緩存中:Hibernate 配置信息。OR映射元數據。
緩存-大:重量級對象 小:輕量級對象
3)調用SessionFactory創建Session的方法
1】用戶自行提供JDBC連接。
Connection con=dataSource.getConnection();
Session s=sessionFactory.openSession(con);
2】讓SessionFactory提供連接
Session s=sessionFactory.openSession();
4)通過Session 接口提供的各種方法來操縱數據庫訪問。
Hibernate 的緩存體系
一級緩存:
Session 有一個內置的緩存,其中存放了被當前工作單元加載的對象。
每個Session 都有自己獨立的緩存,且只能被當前工作單元訪問。
二級緩存:
SessionFactory的外置的可插拔的緩存插件。其中的數據可被多個Session共享訪問。
SessionFactory的內置緩存:存放了映射元數據,預定義的Sql語句。
Hibernate 中Java對象的狀態
1.臨時狀態 (transient)
特征:
1】不處于Session 緩存中
2】數據庫中沒有對象記錄
Java如何進入臨時狀態
1】通過new語句剛創建一個對象時
2】當調用Session 的delete()方法,從Session 緩存中刪除一個對象時。
2.持久化狀態(persisted)
特征:
1】處于Session 緩存中
2】持久化對象數據庫中設有對象記錄
3】Session 在特定時刻會保持二者同步
Java如何進入持久化狀態
1】Session 的save()把臨時-》持久化狀態
2】Session 的load(),get()方法返回的對象
3】Session 的find()返回的list集合中存放的對象
4】Session 的update(),saveOrupdate()使游離-》持久化
3.游離狀態(detached)
特征:
1】不再位于Session 緩存中
2】游離對象由持久化狀態轉變而來,數據庫中可能還有對應記錄。
Java如何進入持久化狀態-》游離狀態
1】Session 的close()方法
2】Session 的evict()方法,從緩存中刪除一個對象。提高性能。少用。
posted @
2009-04-09 23:54 lanxin1020 閱讀(125) |
評論 (0) |
編輯 收藏
也許你聽說過Hibernate的大名,但可能一直不了解它,也許你一直渴望使用它進行開發,那么本文正是你所需要的!在本文中,我向大家重點介紹Hibernate的核心API調用庫,并講解一下它的基本配置。
看完本文后,我相信你對什么是ORM(對像/關系映射)以及它的優點會有一個深刻的認識,我們先通過一個簡單的例子開始來展現它的威力。
正如一些傳統的經典計算機文章大都會通過一個“hello,world”的例子開始講解一樣,我們也不例外,我們也將從一個相對簡單的例子來闡述Hibernate的開發方法,但如果要真正闡述Hibernate的一些重要思想,僅僅靠在屏幕上打印一些字符是遠遠不夠的,在我們的示例程序中,我們將創建一些對象,并將其保存在數據庫中,然后對它們進行更新和查詢。
閱讀導航
“Hello World”“Hello world”示例程序讓您對Hibernate有一個簡單的認識。
理解Hibernate的架構介紹Hibernate接口的主要功能。
核心接口Hibernate有5個核心接口,通過這幾個接口開發人員可以存儲和獲得持久對象,并且能夠進行事務控制
一個重要的術語:TypeType是Hibernate發明者發明的一個術語,它在整個構架中是一個非?;A、有著強大功能的元素,一個Type對象能將一個Java類型映射到數據庫中一個表的字段中去。
策略接口Hibernate與某些其它開源軟件不同的還有一點――高度的可擴展性,這通過它的內置策略機制來實現。
基礎配置Hibernate可以配置成可在任何Java環境中運行,一般說來,它通常被用在2-3層的C/S模式的項目中,并被部署在服務端。
創建一個SessionFactory對象要創建一個SessionFactory對象,必須在Hibernate初始化時創建一個Configuration類的實例,并將已寫好的映射文件交由它處理。
“Hello World”
Hibernate應用程序定義了一些持久類,并且定義了這些類與數據庫表格的映射關系。在我們這個“Hello world”示例程序中包含了一個類和一個映射文件。讓我們看看這個簡單的持久類包含有一些什么?映射文件是怎樣定義的?另外,我們該怎樣用Hibernate來操作這個持久類。
我們這個簡單示例程序的目的是將一些持久類存儲在數據庫中,然后從數據庫取出來,并將其信息正文顯示給用戶。其中Message正是一個簡單的持久類:,它包含我們要顯示的信息,其源代碼如下:
列表1 Message.Java 一個簡單的持久類
package hello;
public class Message {
private Long id;
private String text;
private Message nextMessage;
private Message() {}
public Message(String text) {
this.text = text;
}
public Long getId() {
return id;
}
private void setId(Long id) {
this.id = id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public Message getNextMessage() {
return nextMessage;
}
public void setNextMessage(Message nextMessage) {
this.nextMessage = nextMessage;
}
}
Message類有三個屬性:Message的id 、消息正文、以及一個指向下一條消息的指針。其中id屬性讓我們的應用程序能夠唯一的識別這條消息,通常它等同于數據庫中的主鍵,如果多個Message類的實例對象擁有相同的id,那它們代表數據庫某個表的同一個記錄。在這里我們選擇了長整型作為我們的id值,但這不是必需的。Hibernate允許我們使用任意的類型來作為對象的id值,在后面我們會對此作詳細描述。
你可能注意到Message類的代碼類似于JavaBean的代碼風格,并且它有一個沒有參數的構造函數,在我們以后的代碼中我將繼續使用這種風格來編寫持久類的代碼。
Hibernate會自動管理Message類的實例,并通過內部機制使其持久化,但實際上Message對象并沒有實現任何關于Hibernate的類或接口,因此我們也可以將它作為一個普通的Java類來使用:
Message message = new Message("Hello World");
System.out.println( message.getText() );
以上這段代碼正是我們所期望的結果:它打印“hello world”到屏幕上。但這并不是我們的最終目標;實際上Hibernate與諸如EJB容器這樣的環境在持久層實現的方式上有很大的不同。我們的持久類(Message類)可以用在與容器無關的環境中,不像EJB必須要有EJB容器才能執行。為了能更清楚地表現這點,以下代碼將我們的一個新消息保存到數據庫中去:
Session session = getSessionFactory().openSession();
Transaction tx = session.beginTransaction();
Message message = new Message("Hello World");
session.save(message);
tx.commit();
session.close();
以上這段代碼調用了Hibernate的Session和Transaction接口(關于getSessionFactory()方法我們將會馬上提到)。它相當于我們執行了以下SQL語句:
insert into MESSAGES (MESSAGE_ID, MESSAGE_TEXT, NEXT_MESSAGE_ID)
values (1, 'Hello World', null)
在以上的SQL語句中,MESSAGE_ID字段到底被初始化成了什么值呢?由于我們并沒有在先前的代碼中為message對象的id屬性賦與初始值,那它是否為null呢?實際上Hibernate對id屬性作了特殊處理:由于它是一個對象的唯一標識,因此當我們進行save()調用時,Hibernate會為它自動賦予一個唯一的值(我們將在后面內容中講述它是如何生成這個值的)。
我們假設你已經在數據庫中創建了一個名為MESSAGE的表,那么既然前面這段代碼讓我們將Message對象存入了數據庫中,那么現在我們就要將它們一一取出來。下面這段代碼將按照字母順序,將數據庫中的所有Message對象取出來,并將它們的消息正文打印到屏幕上:
Session newSession = getSessionFactory().openSession();
Transaction newTransaction = newSession.beginTransaction();
List messages =newSession.find("from Message as m order by m.text asc");
System.out.println( messages.size() + " message(s) found:" );
for ( Iterator iter = messages.iterator(); iter.hasNext(); ) {
Message message = (Message) iter.next();
System.out.println( message.getText() );
}
newTransaction.commit();
newSession.close();
在以上這段代碼中,你可能被find()方法的這個參數困擾著:"from Message as m order by m.text asc",其實它是Hibernate自己定義的查詢語言,全稱叫Hibernate Query Language(HQL)。通俗地講HQL與SQL的關系差不多就是方言與普通話之間的關系,咋一看,你會覺得它有點類似于SQL語句。其實在find()調用時,Hibernate會將這段HQL語言翻譯成如下的SQL語句:
select m.MESSAGE_ID, m.MESSAGE_TEXT, m.NEXT_MESSAGE_ID
from MESSAGES m
order by m.MESSAGE_TEXT asc
以下就是運行結果:
1 message(s) found:
Hello World
如果你以前沒有ORM(對象-關系映射)的開發經驗,那你可能想在代碼的某個地方去尋找這段SQL語句,但在Hibernate中你可能會失望:它根本不存在!所有就SQL語句都是Hibernate動態生成的。
也許你會覺得還缺點什么,對!僅憑以上代碼Hibernate是無法將我們的Message類持久化的。我們還需要一些更多的信息,這就是映射定義表!這個表在Hibernate中是以XML格式來體現的,它定義了Message類的屬性是怎樣與數據庫中的MESSAGES表的字段進行一一對應的,列表2是這個示例程序的映射配置文件清單:
列表2:示例程序的對象-關系映射表
?。?xml version="1.0"?>
?。?DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
?。糷ibernate-mapping>
?。糲lass name="hello.Message" table="MESSAGES">
?。糹d name="id" column="MESSAGE_ID">
?。糶enerator class="increment"/>
?。?id>
?。紁roperty name="text" column="MESSAGE_TEXT"/>
?。糾any-to-one name="nextMessage" cascade="all" column="NEXT_MESSAGE_ID"/>
</class>
?。?hibernate-mapping>
以上這個文檔告訴Hibernate怎樣將Message類映射到MESSAGES表中,其中Message類的id屬性與表的MESSAGE_ID字段對應,text屬性與表的MESSAGE_TEXT字段對應,nextMessage屬性是一個多對一的關系,它與表中的NEXT_MESSAGE_ID相對應。
相對于有些開源項目來說,Hibernate的配置文件其實是很容易理解的。你可以輕松地修改與維護它。只要你定義好了持久類與數據庫中表字段的對應關系就行了,Hibernate會自動幫你生成SQL語句來對Message對象進行插入、更新、刪除、查找工作,你可以不寫一句SQL語句,甚至不需要懂得SQL語言!
現在讓我們做一個新的試驗,我們先取出第一個Message對象,然后修改它的消息正文,最后我們再生成一個新的Message對象,并將它作為第一個Message對象的下一條消息,其代碼如下:
列表3 更新一條消息
Session session = getSessionFactory().openSession();
Transaction tx = session.beginTransaction();
// 1 is the generated id of the first message
Message message =(Message) session.load( Message.class, new Long(1) );
message.setText("Greetings Earthling");
Message nextMessage = new Message("Take me to your leader (please)");
message.setNextMessage( nextMessage );
tx.commit();
session.close();
以上這段代碼在調用時,Hibernate內部自動生成如下的SQL語句:
select m.MESSAGE_ID, m.MESSAGE_TEXT, m.NEXT_MESSAGE_ID
from MESSAGES m
where m.MESSAGE_ID = 1
insert into MESSAGES (MESSAGE_ID, MESSAGE_TEXT, NEXT_MESSAGE_ID)
values (2, 'Take me to your leader (please)', null)
update MESSAGES
set MESSAGE_TEXT = 'Greetings Earthling', NEXT_MESSAGE_ID = 2
where MESSAGE_ID = 1
當第一個Message對象的text屬性和nextMessage被程序修改時,請注意Hibernate是如何檢測到這種變化,并如何在數據庫中自動對它更新的。這實際上是Hibernate的一個很有價值的特色,我們把它稱為“自動臟數據檢測”,Hibernate的這個特色使得當我們修改一個持久對象的屬性后,不必顯式地通知Hibernate去將它在數據庫中進行更新。同樣的,當第一個Message對象調用setNextMessage()方法將第二個Message對象作為它的下一條消息的引用時,第二條消息會無需調用save()方法,便可以自動地保存在數據庫中。這種特色被稱為“級聯保存”,它也免去了我們顯式地對第二個Message對象調用save()方法之苦。
如果我們再運行先前的那段將數據庫中所有的Message對象都打印出來的代碼,那它的運行結果如下:
2 message(s) found:
Greetings Earthling
Take me to your leader (please)
“Hello world”示例程序現在介紹完畢。我們總算對Hibernate有了一個簡單的認識,下面我們將回過頭來,對Hibernate的主要API調用作一下簡要的介紹:
理解Hibernate的架構
當你想用Hibernate開發自己的基于持久層的應用時,第一件事情應當是熟悉它的編程接口。Hibernate的API接口設計得盡量簡潔明了,以方便開發人員。然而實際上由于ORM的復雜性,它的API一般都不可能設計得很簡單。但是別擔心,你沒有必要一下子了解所有的Hibernate的API接口。
我們將應用層放在了持久層的上部,實際上在傳統的項目中,應用層充當著持久層的一個客戶端角色。但對于一些簡單的項目來說,應用層和持久層并沒有區分得那么清楚,這也沒什么,在這種情況下你可以將應用層和持久層合并成了一層。
Hibernate的接口大致可以分為以下幾種類型:
· 一些被用戶的應用程序調用的,用來完成基本的創建、讀取、更新、刪除操作以及查詢操作的接口。這些接口是Hibernate實現用戶程序的商業邏輯的主要接口,它們包括Session、Transaction和Query。
· Hibernate用來讀取諸如映射表這類配置文件的接口,典型的代表有Configuration類。
· 回調(Callback)接口。它允許應用程序能對一些事件的發生作出相應的操作,例如Interceptor、Lifecycle和Validatable都是這一類接口。
· 一些可以用來擴展Hibernate的映射機制的接口,例如UserType、CompositeUserType和IdentifierGenerator。這些接口可由用戶程序來實現(如果有必要)。
Hibernate使用了J2EE架構中的如下技術:JDBC、JTA、JNDI。其中JDBC是一個支持關系數據庫操作的一個基礎層;它與JNDI和JTA一起結合,使得Hibernate可以方便地集成到J2EE應用服務器中去。
在這里,我們不會詳細地去討論Hibernate API接口中的所有方法,我們只簡要講一下每個主要接口的功能,如果你想了解得更多的話,你可以在Hibernate的源碼包中的net.sf.hibernate子包中去查看這些接口的源代碼。下面我們依次講一下所有的主要接口:
核心接口
以下5個核心接口幾乎在任何實際開發中都會用到。通過這些接口,你不僅可以存儲和獲得持久對象,并且能夠進行事務控制。
Session接口
Session接口對于Hibernate 開發人員來說是一個最重要的接口。然而在Hibernate中,實例化的Session是一個輕量級的類,創建和銷毀它都不會占用很多資源。這在實際項目中確實很重要,因為在客戶程序中,可能會不斷地創建以及銷毀Session對象,如果Session的開銷太大,會給系統帶來不良影響。但值得注意的是Session對象是非線程安全的,因此在你的設計中,最好是一個線程只創建一個Session對象。
在Hibernate的設計者的頭腦中,他們將session看作介于數據連接與事務管理一種中間接口。我們可以將session想象成一個持久對象的緩沖區,Hibernate能檢測到這些持久對象的改變,并及時刷新數據庫。我們有時也稱Session是一個持久層管理器,因為它包含這一些持久層相關的操作,諸如存儲持久對象至數據庫,以及從數據庫從獲得它們。請注意,Hibernate 的session不同于JSP應用中的HttpSession。當我們使用session這個術語時,我們指的是Hibernate中的session,而我們以后會將HttpSesion對象稱為用戶session。
SessionFactory 接口
這里用到了一個設計模式――工廠模式,用戶程序從工廠類SessionFactory中取得Session的實例。
令你感到奇怪的是SessionFactory并不是輕量級的!實際上它的設計者的意圖是讓它能在整個應用中共享。典型地來說,一個項目通常只需要一個SessionFactory就夠了,但是當你的項目要操作多個數據庫時,那你必須為每個數據庫指定一個SessionFactory。
SessionFactory在Hibernate中實際起到了一個緩沖區的作用,它緩沖了Hibernate自動生成的SQL語句和一些其它的映射數據,還緩沖了一些將來有可能重復利用的數據。
Configuration 接口
Configuration接口的作用是對Hibernate進行配置,以及對它進行啟動。在Hibernate的啟動過程中,Configuration類的實例首先定位映射文檔的位置,讀取這些配置,然后創建一個SessionFactory對象。
雖然Configuration接口在整個Hibernate項目中只扮演著一個很小的角色,但它是啟動hibernate時你所遇到的每一個對象。
Transaction 接口
Transaction接口是一個可選的API,你可以選擇不使用這個接口,取而代之的是Hibernate的設計者自己寫的底層事務處理代碼。 Transaction接口是對實際事務實現的一個抽象,這些實現包括JDBC的事務、JTA中的UserTransaction、甚至可以是CORBA事務。之所以這樣設計是能讓開發者能夠使用一個統一事務的操作界面,使得自己的項目可以在不同的環境和容器之間方便地移值。
Query和Criteria接口
Query接口讓你方便地對數據庫及持久對象進行查詢,它可以有兩種表達方式:HQL語言或本地數據庫的SQL語句。Query經常被用來綁定查詢參數、限制查詢記錄數量,并最終執行查詢操作。
Criteria接口與Query接口非常類似,它允許你創建并執行面向對象的標準化查詢。
值得注意的是Query接口也是輕量級的,它不能在Session之外使用。
Callback 接口
當一些有用的事件發生時――例如持久對象的載入、存儲、刪除時,Callback接口會通知Hibernate去接收一個通知消息。一般而言,Callback接口在用戶程序中并不是必須的,但你要在你的項目中創建審計日志時,你可能會用到它。
一個重要的術語:Type
Hibernate的設計者們發明了一個術語:Type,它在整個構架中是一個非?;A、有著強大功能的元素。一個Type對象能將一個Java類型映射到數據庫中一個表的字段中去(實際上,它可以映射到表的多個字段中去)。持久類的所有屬性都對應一個type。這種設計思想使用Hibernate有著高度的靈活性和擴展性。
Hibernate內置很多type類型,幾乎包括所有的Java基本類型,例如Java.util.Currency、Java.util.calendar、byte[]和Java.io.Serializable。
不僅如此,Hibernate還支持用戶自定義的type,通過實現接口UserType和接口CompositeUserType,你可以加入自己的type。你可以利用這種特色讓你的項目中使用自定義的諸如Address、Name這樣的type,這樣你就可以獲得更大的便利,讓你的代碼更優雅。自定義type在Hibernate中是一項核心特色,它的設計者鼓勵你多多使用它來創建一個靈活、優雅的項目!
策略接口
Hibernate與某些其它開源軟件不同的還有一點――高度的可擴展性,這通過它的內置策略機制來實現。當你感覺到Hibernate的某些功能不足,或者有某些缺陷時,你可以開發一個自己的策略來替換它,而你所要做的僅僅只是繼承它的某個策略接口,然后實現你的新策略就可以了,以下是它的策略接口:
· 主鍵的生成 (IdentifierGenerator 接口)
· 本地SQL語言支持 (Dialect 抽象類)
· 緩沖機制 (Cache 和CacheProvider 接口)
· JDBC 連接管理 (ConnectionProvider接口)
· 事務管理 (TransactionFactory, Transaction, 和 TransactionManagerLookup 接口)
· ORM 策略 (ClassPersister 接口)
· 屬性訪問策略 (PropertyAccessor 接口)
· 代理對象的創建 (ProxyFactory接口)
Hibernate為以上所列的機制分別創建了一個缺省的實現,因此如果你只是要增強它的某個策略的功能的話,只需簡單地繼承這個類就可以了,沒有必要從頭開始寫代碼。
以上就是Hibernate的一些核心接口,但當我們真正開始用它進行開發時,你的腦海里可能總會有一個疑問:我是通過什么方式,并從哪里取得Session的呢?以下我們就解答這個問題。
基礎配置
現在回顧一下我們先前的內容:我們寫出了一個示例程序,并簡要地講解了Hibernate的一些核心類。但要真正使你的項目運行起來,還有一件事必須要做:配置。Hibernate可以配置成可在任何Java環境中運行,一般說來,它通常被用在2-3層的C/S模式的項目中,并被部署在服務端。在這種項目中,Web瀏覽器、或Java GUI程序充當者客戶端。盡管我們的焦點主要是集中在多層web應用,但實際上在一些基于命令行的應用中也可以使用Hibernate。并且,對Hibernate的配置在不同的環境下都會不同,Hibernate運行在兩種環境下:可管理環境和不可管理環境
· 可管理環境――這種環境可管理如下資源:池資源管理,諸如數據庫連接池和,還有事務管理、安全定義。一些典型的J2EE服務器(JBoss、Weblogic、WebSphere)已經實現了這些。
· 不可管理環境――只是提供了一些基本的功能,諸如像Jetty或Tomcat這樣的servlet容器環境。一個普通的Java桌面應用或命令行程序也可以認為是處于這種環境下。這種環境不能提供自動事務處理、資源管理或安全管理,這些都必須由應用程序自己來定義。
Hibernate的設計者們將這兩種環境設計了一個統一的抽象界面,因此對于開發者來說只有一種環境:可管理環境。如果實際項目是建立在諸如Tomcat這類不可管理的環境中時,那Hibernate將會使用它自己的事務處理代碼和JDBC連接池,使其變為一個可管理環境。
對于可管理的環境而言,Hibernate會將自己集成在這種環境中。對于開發者而言,你所要做的工作非常簡單:只需從一個Configuration類中創建一個SessionFactory類就可以了。
創建一個SessionFactory對象
為了能創建一個SessionFactory對象,你必須在Hibernate初始化時創建一個Configuration類的實例,并將已寫好的映射文件交由它處理。這樣,Configuration對象就可以創建一個SessionFactory對象,當SessionFactory對象創建成功后,Configuration對象就沒有用了,你可以簡單地拋棄它。如下是示例代碼:
Configuration cfg = new Configuration();
cfg.addResource("hello/Message.hbm.xml");
cfg.setProperties( System.getProperties() );
SessionFactory sessions = cfg.buildSessionFactory();
在以上代碼中,Message.hb.xml這個映射文件的位置比較特殊,它與當前的classpath相關。例如classpath包含當前目錄,那在上述代碼中的Message.hbm.xml映射文件就可以保存在當前目錄下的hello目錄中。
作為一種約定,Hibernate的映射文件默認以.htm.xml作為其擴展名。另一個約定是堅持為每一個持久類寫一個配置文件,想一想如果你將所有持久類的映射寫入一個單獨的配置文件中的話,那這個配置文件肯定非常龐大,不易維護。但這里又出現了一個新問題:如果為每個類寫一個配置文件的話,這么多的配置文件應該存放在哪里呢?
Hibernate推薦你將每個映射文件保存在與持久類相同的目錄下,并且與持久類同名。例如我們第一個示例程序中的Message持久類放在hello目錄下,那你必須在這個目錄下存放名為Message.hbm.xml的映射文件。這樣一個持久類都有自己的一個映射文件,避免了出現像struts項目中的“struts-config.xml地獄”的情況。如果你不遵循這種規定,那你必須手動地用addResource()方法將一個個的映射文件載入;但你如果遵循這種規定,那你可以方便地用addClass()方法同時將持久類和它的映射文件載入,以下是體現這種便利性的示例代碼:
SessionFactory sessions = new Configuration()
.addClass(org.hibernate.auction.model.Item.class)
.addClass(org.hibernate.auction.model.Category.class)
.addClass(org.hibernate.auction.model.Bid.class)
.setProperties( System.getProperties() )
.buildSessionFactory();
當然,Hibernate的映射文件還有很多其它的配置選項,比如數據庫連接的設定,或是能夠改變Hibernate運行時行為的一些設定。所有的設置可能是非常龐雜的,足以讓你喘不過氣來,但是不必擔心,因為Hibernate為絕大多數值都設定了一個合理缺省值,你只需要修改這些配置文件中的極小一部分值。
你可以通過以下幾種方式來修改Hibernate的系統配置參數:
· 將一個Java.util.Properties實例作為參數傳給Configuration類的setProperties()方法。
· 在Hibernate啟動時用Java –Dproperty=value的方式設置值。
· 在classpath可以找到的路徑下創建一個名為hibernate.properties的配置文件。
· 在classpath可以找到的路徑下創建一個名為hibernate.cfg.xml的文件,并在其<property>標簽中定義屬性值。
以上就是對Hibernate的一個大致介紹,如果你想知道得更多,那本文還是遠遠不夠的,我將陸續推出更多關于Hibernate的資料。但有一點是毫無疑問的:它的確是一個非常優秀的持久層解決方案!
posted @
2009-04-09 23:50 lanxin1020 閱讀(133) |
評論 (0) |
編輯 收藏
關鍵字: hibernate學習筆記
在用Hibernate的時候,時不時會因為沒有處理好實體對象的狀態而犯一些莫名其妙的異常,在這里對實體對象的各種狀態整理一下,希望能有所幫助。
Hibernate實體對象,即指Hibernate O/R影射關系中的域對象 即O/R中的"O"。在Hibrenate實體對象的生命周期中存在著三中狀態,即:
1:自由狀態(Transient)。
2:持久狀態(Persistent)。
3:游離狀態(Detached)。
1:自由狀態(Transient)
自由狀態(Transient),是指實體對象在內存中自由存在,他與數據庫的記錄無關。如:
- TUser user = new TUser();
- user.setName("MyName");
TUser user = new TUser();
user.setName("MyName");
這里的user對象只是一個非常普通的java對象,與數據庫中的記錄沒有任何關系。
2:持久狀態(Persistent)
持久狀態(Persistent),即實體對象處于Hibernate框架的管理狀態,實體對象被納入Hibernate的實體容器中管理。處于
持久狀態的對象,其更變將由Hibernate固化到數據庫中。如:
-
- TUser user_1 = new TUser();
- TUser user_2 = new TUser();
-
- user_1.setName("Name_1");
- user_2.setName("Name_2");
-
- Transaction tx = session.begintransaction();
- session.save(user_1);
-
-
-
- tx.commit();
-
-
//創建兩個處于自由狀態的實體對象。
TUser user_1 = new TUser();
TUser user_2 = new TUser();
user_1.setName("Name_1");
user_2.setName("Name_2");
Transaction tx = session.begintransaction();
session.save(user_1);
//通過session的save方法,user_1對象已經被納入Hibernate的實體管理容器,處于持久化狀
//態(Persistent),這時候對user_1對象的任何修改都將被同步到數據庫中。
tx.commit();
//而user_2仍然才處于自由狀態(Transient),不受Hibernate框架的管理。
從上面看到,處于
自由狀態的實體對象,可以通過Hibernate的Session.sava方法轉化為
持久狀態。
除了用Session.save方法外,還可以通過其他方法來獲取一個
持久狀態的對象,那就是直接通過Hibernate加載的對象,通過Session.load方法,可以直接加載一個處于
持久狀態的實體對象。如下:
- TUser user = Session.load(TUser.class,new Integer(1));
-
-
TUser user = Session.load(TUser.class,new Integer(1));
//在load方法沒返回之前,就已經先把對象納入Hibernate的管理范圍,所以這里的user
//已經處于持久狀態。
從上面的代碼可以看出,處于
持久狀態的實體對象一定要和Session關聯,并處于該Session的有效期內。
3:游離狀態(Detached)
處于
持久狀態的實體對象,在其關聯的Session關閉以后,此實體對象就處于
游離狀態,
- TUser user = new TUser();
- user.setName("name_1");
- Transaction tx = session.begintransaction();
- session.save(user);
- tx.commit();
- session.close();
-
-
TUser user = new TUser();
user.setName("name_1");
Transaction tx = session.begintransaction();
session.save(user);//把自由狀態的實體對象user轉為持久狀態,
tx.commit();
session.close();
//session關閉以后,處于持久狀態的實體對象user將轉為游離狀態。
//因為此時user已經和session脫離關系。
由上面可以看到實體對象的
游離狀態是在對象和它所寄宿的Session脫離關系后形成的,但處于
自由狀態的實體對象也沒有和任何session有關聯,那么他們兩者有什么區別呢?關鍵的就在我們對
自由狀態的實體對象執行了Session.save方法,
當我們執行
- TUser user = new TUser();
TUser user = new TUser();
時,我們只是創建了一個普通的對象,他并沒有和數據庫里的任何一條記錄對應,當我們執行
Session.save以后,Hibernate就為user設置了一個主鍵,就是user.Id屬性,通過這個屬性,Hibernate就把user對象和數據庫里的記錄關聯起來,所以
自由狀態和
游離狀態的基本區別就是 處于
游離狀態的實體對象,在數據庫里有對應的記錄,因此它可以通過和session關聯再次轉為
持久狀態。
三種狀態的轉化
自由狀態-->持久狀態:可以通過Session.sava方法來轉換。
持久狀態-->游離狀態:可以通過Session.close方法來關閉session,獲取游離狀態的對象
持久狀態-->自由狀態:可以通過Session.delete方法來刪除實體對象對應的數據庫記錄,使實體對象處于自由狀態。
補充一下,有時可能會想,可以通過認為的個處于
自由狀態的實體對象設置一個Id值
user.Id=1
,來人為的創建一個
游離狀態的對象。
這里注意一點,我們可以人為地給實體對象設置Id值,我我們無法知道這個ID值在數據庫里有沒有對應的記錄,如果沒有,就算我們人為地設置了Id,也不能說一個有ID的實體對象就是一個
游離狀態的對象。
posted @
2009-04-08 12:02 lanxin1020 閱讀(255) |
評論 (0) |
編輯 收藏
Introducing to Spring Framework
作者:Rod Johnson
譯者:yanger,taowen
校對:taowen
關于Spring Framework,今年夏天你可能已經聽見很多的議論。在本文中,我將試圖解釋Spring能完成什么,和我怎么會認為它能幫助你開發J2EE應用程序。
又來一個framework?
你可能正在想“不過是另外一個的framework”。當已經有許多開放源代碼(和專有) J2EE framework時,為什么你還要耐下心子讀這篇文章或去下載Spring Framework?
我相信Spring是獨特的,有幾個原因:
它關注的領域是其他許多流行的Framework未曾關注的。Spring要提供的是一種管理你的業務對象的方法。
Spring既是全面的又是模塊化的。Spring有分層的體系結構,這意味著你能選擇僅僅使用它任何一個獨立的部分,而它的架構又是內部一致。因此你能從你的學習中,得到最大的價值。例如,你可能選擇僅僅使用Spring來簡單化JDBC的使用,或用來管理所有的業務對象。
它的設計從一開始就是要幫助你編寫易于測試的代碼。Spring是使用測試驅動開發的工程的理想框架。
Spring不會給你的工程添加對其他的框架依賴。Spring也許稱得上是個一站式解決方案,提供了一個典型應用所需要的大部分基礎架構。它還涉及到了其他framework沒有考慮到的內容。
盡管它僅僅是一個從2003年2月才開始的開源項目,但Spring有深厚的歷史根基。這個開源工程是起源自我在2002年晚些時候出版的《Expert One-on-One J2EE設計與開發》書中的基礎性代碼。這本書展示了Spring背后的基礎性架構思想。然而,對這個基礎架構的概念可以追溯到2000年的早些時候,并且反映了我為一系列商業工程開發基礎結構的成功經驗。
2003年1月,Spring已經落戶于SourceForge上了。現在有10個開發人員,其中6個是高度投入的積極分子。
Spring架構上的好處
在我們進入細節之前,讓我們來看看Spring能夠給工程帶來的種種好處:
Spring能有效地組織你的中間層對象,不管你是否選擇使用了EJB。如果你僅僅使用了Struts或其他為J2EE的 API特制的framework,Spring致力于解決剩下的問題。
Spring能消除在許多工程中常見的對Singleton的過多使用。根據我的經驗,這是一個很大的問題,它降低了系統的可測試性和面向對象的程度。
通過一種在不同應用程序和項目間一致的方法來處理配置文件,Spring能消除各種各樣自定義格式的屬性文件的需要。曾經對某個類要尋找的是哪個魔法般的屬性項或系統屬性感到不解,為此不得不去讀Javadoc甚至源編碼?有了Spring,你僅僅需要看看類的JavaBean屬性。 Inversion of Control的使用(在下面討論)幫助完成了這種簡化。
通過把對接口編程而不是對類編程的代價幾乎減少到沒有,Spring能夠促進養成好的編程習慣。
Spring被設計為讓使用它創建的應用盡可能少的依賴于他的APIs。在Spring應用中的大多數業務對象沒有依賴于Spring。
使用Spring構建的應用程序易于單元測試。
Spring能使EJB的使用成為一個實現選擇,而不是應用架構的必然選擇。你能選擇用POJOs或local EJBs來實現業務接口,卻不會影響調用代碼。
Spring幫助你解決許多問題而無需使用EJB。Spring能提供一種EJB的替換物,它們適用于許多web應用。例如,Spring能使用AOP提供聲明性事務管理而不通過EJB容器,如果你僅僅需要與單個數據庫打交道,甚至不需要一個JTA實現。
Spring為數據存取提供了一個一致的框架,不論是使用的是JDBC還是O/R mapping產品(如Hibernate)。
Spring確實使你能通過最簡單可行的解決辦法來解決你的問題。而這是有有很大價值的。
Spring做了些什么?
Spring提供許多功能,在此我將依次快速地展示其各個主要方面。
任務描述
首先,讓我們明確Spring范圍。盡管Spring覆蓋了許多方面,但我們對它應該涉什么,什么不應該涉及有清楚的認識。
Spring的主要目的是使J2EE易用和促進好編程習慣。
Spring不重新輪子。因此,你發現在Spring中沒有logging,沒有連接池,沒有分布式事務調度。所有這些東西均有開源項目提供(例如我們用于處理所有日志輸出的Commons Logging以及Commons DBCP),或由你的應用程序服務器提供了。出于同樣的的原因,我們沒有提供 O/R mapping層。對于這個問題已經有了像Hibernate和JDO這樣的優秀解決方案。
Spring的目標就是讓已有的技術更加易用。例如,盡管我們沒有底層事務協調處理,但我們提供了一個抽象層覆蓋了JTA或任何其他的事務策略。
Spring沒有直接和其他的開源項目競爭,除非我們感到我們能提供新的一些東西。例如,象許多開發人員一樣,我們從來沒有對Struts感到高興過,并且覺得到在MVC web framework中還有改進的余地。在某些領域,例如輕量級的IoC容器和AOP框架,Spring確實有直接的競爭,但是在這些領域還沒有已經較為流行的解決方案。(Spring在這些領域是開路先鋒。)
Spring也得益于內在的一致性。所有的開發者都在唱同樣的的贊歌,基礎想法依然與Expert One-on-One J2EE設計與開發中提出的差不多。 并且我們已經能夠在多個領域中使用一些中心的概念,例如Inversion of Control。
Spring在應用服務器之間是可移植的。當然保證可移植性總是一種挑戰,但是我們避免使用任何平臺特有或非標準的東西,并且支持在WebLogic,Tomcat,Resin,JBoss,WebSphere和其他的應用服務器上的用戶。
Inversion of Control 容器
Spring設計的核心是 org.springframework.beans 包, 它是為與JavaBeans一起工作而設計的。 這個包一般不直接被用戶使用,而是作為許多其他功能的基礎。
下一個層面高一些的抽象是"Bean Factory"。一個Spring bean factory 是一個通用的Factory,它使對象能夠按名稱獲取,并且能管理對象之間的關系。
Bean factories 支持兩種模式的對象:
Singleton:在此模式中,有一個具有特定名稱的共享對象實例,它在查找時被獲取。這是默認的,而且是最為經常使用的。它對于無狀態對象是一種理想的模式。
Prototype:在此模式中,每次獲取將創建一個獨立的對象。例如,這可以被用于讓用戶擁有他們自己的對象。
由于 org.springframwork.beans.factory.BeanFactory是一個簡單的接口,它能被大量底層存儲方法實現。你能夠方便地實現你自己的BeanFactory,盡管很少用戶需要這么做。最為常用的BeanFactory定義是:
XmlBeanFactory: 可解析簡單直觀的定義類和命名對象屬性的XML結構。 我們提供了一個DTD來使編寫更容易。
ListableBeanFactoryImpl:提供了解析存放在屬性文件中的bean定義的能力,并且可通過編程創建BeanFactories。
每個bean定義可能是一個POJO(通過類名和JavaBean初始屬性定義),或是一個FactoryBean。FactoryBean接口添加了一個間接層。通常,這用于創建使用AOP或其他方法的代理對象:例如,添加聲明性事務管理的代理。(這在概念上和EJB的interception相似,但實現得更簡單。)
BeanFactories能在一個層次結構中選擇性地參與,繼承ancestor(祖先)的定義。這使得在整個應用中公共配置的共享成為可能,雖然個別資源,如controller servlets,還擁有他們自己的獨立的對象集合。
這種使用JavaBeans的動機在《Expert One-on-One J2EE Design and Development》的第四章中有描述,在TheServerSide網站上的有免費的PDF版本(http://www.theserverside.com/resources/article.jsp?l=RodJohnsonInterview)。
通過BeanFactory概念,Spring成為一個Inversion of Control的容器。(我不怎么喜歡container這個詞,因為它使人聯想到重量級容器,如EJB容器。Spring的BeanFactory是一個可通過一行代碼創建的容器,并且不需要特殊的部署步驟。)
Inversion of Control背后的概念經常表述為Hollywood原則的:“Don’t call me, I’ll call you。” IoC將控制創建的職責搬進了框架中,并把它從應用代碼脫離開來。涉及到配置的地方,意思是說在傳統的容器體系結構中,如EJB,一個組件可以調用容器并問“我需要它給我做工作的對象X在哪里?”;使用IoC容器則只需指出組件需要X對象,在運行時容器會提供給它。容器是通過查看方法的參數表(例如JavaBean的屬性)做到的,也可能根據配置數據如XML。
IoC有幾個重要的好處,例如:
因為組件不需要在運行時間尋找合作者,所以他們可以更簡單的編寫和維護。在Spring版的IoC里,組件通過暴露JavaBean的setter方法表達他們依賴的其他組件。這相當于EJB通過JNDI來查找,EJB查找需要開發人員編寫代碼。
同樣原因,應用代碼更容易測試。JavaBean屬性是簡單的,屬于Java核心的,并且是容易測試的:僅編寫一個自包含的Junit測試方法用來創建對象和設置相關屬性即可。
一個好的IoC實現保留了強類型。如果你需要使用一個通用的factory來尋找合作者,你必須通過類型轉換將返回結果轉變為想要的類型。這不是一個大不了的問題,但是不雅觀。使用IoC,你在你的代碼中表達了強類型依賴,框架將負責類型轉換。這意味著在框架配置應用時,類型不匹配將導致錯誤;在你的代碼中,你無需擔心類型轉換異常。
大部分業務對象不依賴于IoC容器的APIs。這使得很容易使用遺留下來的代碼,且很容易的使用對象無論在容器內或不在容器內。例如,Spring用戶經常配置Jakarta Commons DBCP數據源為一個Spring bean:不需要些任何定制代碼去做這件事。我們說一個IoC容器不是侵入性的:使用它并不會使你的代碼依賴于它的APIs。任何JavaBean在Spring bean factory中都能成為一個組件。
最后應該強調的是,IoC 不同于傳統的容器的體系結構,如EJB,應用代碼最小程度地依靠于容器。這意味著你的業務對象可以潛在的被運行在不同的IoC 框架上——或者在任何框架之外——不需要任何代碼的改動。
以我和其他Spring用戶的經驗來說,再怎么強調IoC給應用程序代碼帶來的好處也不為過。
IoC不是一個新概念,但是它在J2EE團體里面剛剛到達黃金時間。 有一些可供選擇的IoC 容器: 例如 Apache Avalon, PicoContainer 和 HiveMind。Avalon 從沒怎么流行,盡管它很強大而且有很長的歷史。Avalon相當的重和復雜,并且看起來比新的IoC解決方案更具侵入性。 PicoContainer是一個輕量級而且更強調通過構造函數表達依賴性而不是JavaBean 屬性。 與 Spring不同,它的設計允許每個類型一個對象的定義(可能是因為它拒絕任何Java代碼外的元數據導致的局限性)。在Spring, PicoContainer 和其他 IoC frameworks之間做比較,可參看文章Spring網站上的 "The Spring Framework - A Lightweight Container"位于http://www.springframework.org/docs/lightweight_container.html。這個頁面里面包含了PicoContainer站點的鏈接 。
Spring BeanFactories 是非常輕量級的。用戶已經成功地將他們應用在applets和單獨的Swing應用中。(它們也很好地工作在 EJB容器中。) 沒有特殊的部署步驟和察覺得到的啟動時間。這個能力表明一個容器在應用的任何層面幾乎立即可以發揮非常大的價值。
Spring BeanFactory 概念貫穿于Spring始終, 而且是Spring如此內在一致的關鍵原因。在IoC容器中,Spring也是唯一的,它使用IoC作為基礎概念貫穿于整個功能豐富的框架。
對應用開發人員,最重要的是,一個或多個BeanFactory提供了一個定義明確的業務對象層。這類似于local session bean層,但比它更簡單。與EJBs不同,在這個層中的對象可能是相關的,并且他們的關系被擁有它們的factory管理。有一個定義明確的業務對象層對于成功的體系結構是非常重要的。
Spring ApplicationContext 是BeanFactory的子接口,為下列東西提供支持:
信息查找,支持著國際化
事件機制,允許發布應用對象以及可選的注冊以接收到事件
可移植的文件和資源訪問
XmlBeanFactory 例子
Spring用戶通常在XML的“bean定義”文件中配置他們的應用。Spring的XML bean定義文檔的根是<beans> 元素。該元素包含一個或多個 <bean>定義。我們一般給每個bean定義的指定類和屬性。我們還必須指定ID作為標識,這將成為在代碼中使用該bean的名字。
讓我們來看一個簡單的例子,它配置了三個應用程序對象,之間的關系在J2EE應用中常常能夠看到:
J2EE DataSource
使用DataSource的DAO
在處理過程中使用DAO的業務對象
在下面的例子中,我們使用一個來自Jakarta Commons DBCP項目的BasicDataSource。這個class(和其他許多已有的 class一樣)可以簡單地被應用在Spring bean factory中,只要它提供了JavaBean格式的配置。需要在shutdown時被調用的Close方法可通過Spring的"destroy-method"屬性被注冊,以避免BasicDataSource需要實現任何Spring 的接口。
代碼:
<beans>
<bean id="myDataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName"><value>com.mysql.jdbc.Driver</value></property>
<property name="url"><value>jdbc:mysql://localhost:3306/mydb</value></property>
<property name="username"><value>root</value></property>
</bean>
BasicDataSource中我們感興趣的所有屬性都是String類型的,因此我們用<value>元素來指定他們的值。如果必要的話,Spring使用標準的 JavaBean屬性編輯器機制來把String轉換為其他的類型。
現在,我們定義DAO,它有一個對DataSource的bean引用。Bean間關系通過<ref>元素來指定:
代碼:
<bean id="exampleDataAccessObject"
class="example.ExampleDataAccessObject">
<property name="dataSource"><ref bean="myDataSource"/></property>
</bean>
The business object has a reference to the DAO, and an int property (exampleParam):
<bean id="exampleBusinessObject"
class="example.ExampleBusinessObject">
<property name="dataAccessObject"><ref bean="exampleDataAccessObject"/></property>
<property name="exampleParam"><value>10</value></property>
</bean>
</beans>
對象間的關系一般在配置中明確地設置,象這個例子一樣。我們認為這樣做是件好事情。然而Spring還提供了我們稱做"autowire"的支持, 一個 la PicoContainer,其中它指出了bean間的依賴關系。這樣做的局限性——PicoContainer也是如此——是如果有一個特殊類型的多個Bean,要確定那個類型所依賴的是哪個實例是不可能。好的方面是,不滿足的依賴可以在factory初始化后被捕獲到。(Spring 也為顯式的配置提供了一種可選的依賴檢查,它可以完成這個目的)
在上面的例子中,如果我們不想顯式的編寫他們的關系,可使用如下的autowire特性:
代碼:
<bean id="exampleBusinessObject"
class="example.ExampleBusinessObject"
autowire="byType">
<property name="exampleParam"><value>10</value></property>
</bean>
使用這個特性,Spring會找出exampleBusinessObject的dataSource屬性應該被設置為在當前BeanFactory中找到的DataSource實現。在當前的BeanFactory中,如果所需要類型的bean不存在或多于一個,將產生一個錯誤。我們依然要設置 exampleParam屬性,因為它不是一個引用。
Autowire支持和依賴檢查剛剛加入CVS并將在Spring 1.0 M2(到10/20,2003)中提供。本文中所討論的所有其他特性都包含在當前1.0 M1版本中。
把管理從Java代碼中移出來比硬編碼有很大的好處,因為這樣可以只改變XML文件而無需改變一行Java代碼。例如,我們可以簡單地改變 myDataSource的bean定義引用不同的bean class以使用別的連接池,或者一個用于測試的數據源。 XML節變成另一種,我們可以用 Spring的JNDI location FactoryBean從應用服務器獲取一個數據源。
現在讓我們來看看例子中業務對象的java 代碼。注意下面列出的代碼中沒有對Spring的依賴。不像EJB容器,Spring BeanFactory不具有侵入性:在應用對象里面你通常不需要對Spring的存在硬編碼。
代碼:
public class ExampleBusinessObject implements MyBusinessObject {
private ExampleDataAccessObject dao;
private int exampleParam;
public void setDataAccessObject(ExampleDataAccessObject dao) {
this.dao = dao;
}
public void setExampleParam(int exampleParam) {
this.exampleParam = exampleParam;
}
public void myBusinessMethod() {
// do stuff using dao
}
}
注意那些property setter,它們對應于bean定義文檔中的XML引用。這些將在對象被使用之前由Spring調用。
這些應用程序的bean不需要依賴于Spring:他們不需要實現任何Spring的接口或者繼承Spring的類。他們只需要遵守JavaBeans的命名習慣。在Spring 應用環境之外重用它們是非常簡單的,例如,在一個測試環境中。只需要用它們的缺省構造函數實例化它們,并且通過調用 setDataSource()和setExampleParam()手工設置它的屬性。如果你想以一行代碼支持程序化的創建,只要你有一個無參數的構造器,你就可以自由定義其他需要多個屬性的構造函數。
注意在業務接口中沒有聲明將會一起使用的JavaBean屬性。 他們是一個實現細節。我們可以“插入”帶有不同bean屬性的不同的實現類而不影響連接著的對象或者調用的代碼。
當然,Spring XML bean factories 有更多的功能沒有在這里描述,但是,應當讓你對基本使用有了一些感覺。以及,簡單的屬性,有 JavaBean屬性編輯器的屬性,Spring可以自動處理lists,maps和java.util.Properties。
Bean factories 和application contexts 通常和J2EE server定義的一個范圍相關聯,例如:
Servlet context.:在spring 的MVC 框架里, 每一個包含common objects的web 應用都定義有一個應用程序的 context。Spring提供了通過listener或者servlet實例化這樣的context的能力而不需要依賴于Spring 的MVC 框架,因而它也可以用于Struts,WebWork 或者其他的web框架之中。
A Servlet:在Spring MVC 框架里每一個servlet控制器都有它自己的應用程序context,派生于根(全應用程序范圍的)應用程序context。在Struts或者其他MVC框架中實現這些也很容意。
EJB:Spring 為EJB提供方便的超類,它們簡化了EJB的創建并且提供了一個從EJB Jar 文件中的XML文檔載入的BeanFactory。
這些J2EE規范提供的hook通常避免了使用Singleton來創造一個bean factory。
然而,如果我們愿意的話可以用代碼創建一個BeanFactory,雖然是沒有什么意義的。例如,我們在以下三行代碼中可以創建bean factory并且得到一個業務對象的引用:
代碼:
InputStream is = getClass().getResourceAsStream("myFile.xml");
XmlBeanFactory bf = new XmlBeanFactory(is);
MyBusinessObject mbo = (MyBusinessObject) bf.getBean("exampleBusinessObject");
這段代碼將能工作在一個應用服務器之外:甚至不依賴J2EE,因為Spring 的IoC容器是純java的。
JDBC 抽象和數據存儲異常層次
數據訪問是Spring 的另一個閃光點。
JDBC 提供了還算不錯的數據庫抽象,但是需要用痛苦的API。這些問題包括:
需要冗長的錯誤處理代碼來確保ResultSets,Statements以及(最重要的)Connections在使用后關閉。這意味著對JDBC的正確使用可以快速地導致大量的代碼量。它還是一個常見的錯誤來源。Connection leak可以在有負載的情況下快速宕掉應用程序。
SQLException相對來說不能說明任何問題。JDBC不提供異常的層次,而是用拋出SQLException來響應所有的錯誤。找出到底哪里出錯了——例如,問題是死鎖還是無效的SQL?——要去檢查SQLState或錯誤代碼。這意味著這些值在數據庫之間是變化的。
Spring用兩種方法解決這些問題:
提供API,把冗長乏味和容易出錯的異常處理從程序代碼移到框架之中??蚣芴幚硭械漠惓L幚?;程序代碼能夠集中精力于編寫恰當的SQL和提取結果上。
為你本要處理SQLException程序代碼提供有意義的異常層次。當Spring第一次從數據源取得一個連接時,它檢查元數據以確定數據庫。它使用這些信息把SQLException映射為自己從org.springframework.dao.DataAccessException派生下來的類層次中正確的異常。因而你的代碼可以與有意義的異常打交道,并且不需要為私有的SQLState或者錯誤碼擔心。Spring的數據訪問異常不是JDBC特有的,因而你的DAO并不一定會因為它們可能拋出的異常而綁死在JDBC上。
Spring提供兩層JDBC API。第一個時,在org.springframework.jdbc.core包中,使用回調機制移動控制權——并且因而把錯誤處理和連接獲取和釋放——從程序的代碼移到了框架之中。這是一種不同的Inversion of Control,但是和用于配置管理的幾乎有同等重要的意義。
Spring使用類似的回調機制關注其他包含特殊獲取和清理資源步驟的API,例如JDO(獲取和釋放是由PersistenceManager完成的),事務管理(使用JTA)和JNDI。Spring中完成這些回調的類被稱作template。
例如,Spring的JdbcTemplate對象能夠用于執行SQL查詢并且在如下的列表中保存結果:
代碼:
JdbcTemplate template = new JdbcTemplate(dataSource);
final List names = new LinkedList();
template.query("SELECT USER.NAME FROM USER",
new RowCallbackHandler() {
public void processRow(ResultSet rs) throws SQLException {
names.add(rs.getString(1));
}
});
注意回調中的程序代碼是能夠自由拋出SQLException的:Spring將會捕捉到這些異常并且用自己的類層次重新拋出。程序的開發者可以選擇哪個異常,如果有的話,被捕捉然后處理。
JdbcTemplate提供許多支持不同情景包括prepared statements和批量更新的方法。Spring的JDBC抽象有比起標準JDBC來說性能損失非常小,甚至在當應用中需要的結果集數量很大的時候。
在org.springframework.jdbc.object包中是對JDBC的更高層次的抽象。這是建立在核心的JDBC回調功能基礎紙上的,但是提供了一個能夠對RDBMS操作——無論是查詢,更新或者是存儲過程——使用Java對象來建模的API。這個API部分是受到JDO查詢API的影響,我發現它直觀而且非常有用。
一個用于返回User對象的查詢對象可能是這樣的:
代碼:
class UserQuery extends MappingSqlQuery {
public UserQuery(DataSource datasource) {
super(datasource, "SELECT * FROM PUB_USER_ADDRESS WHERE USER_ID = ?");
declareParameter(new SqlParameter(Types.NUMERIC));
compile();
}
// Map a result set row to a Java object
protected Object mapRow(ResultSet rs, int rownum) throws SQLException {
User user = new User();
user.setId(rs.getLong("USER_ID"));
user.setForename(rs.getString("FORENAME"));
return user;
}
public User findUser(long id) {
// Use superclass convenience method to provide strong typing
return (User) findObject(id);
}
}
這個類可以在下面用上:
代碼:
User user = userQuery.findUser(25);
這樣的對象經??梢杂米鱀AO的inner class。它們是線程安全的,除非子類作了一些超出常規的事情。
在org.springframework.jdbc.object包中另一個重要的類是StoredProcedure類。Spring讓存儲過程通過帶有一個業務方法的Java類進行代理。如果你喜歡的話,你可以定義一個存儲過程實現的接口,意味著你能夠把你的程序代碼從對存儲過程的依賴中完全解脫出來。
Spring數據訪問異常層次是基于unchecked(運行時)exception的。在幾個工程中使用了Spring之后,我越來越確信這個決定是正確的。
數據訪問異常一般是不可恢復的。例如,如果我們不能鏈接到數據庫,某個業務對象很有可能就不能完成要解決的問題了。一個可能的異常是 optimistic locking violation,但是不是所有的程序使用optimistic locking。強制編寫捕捉其無法有效處理的致命的異常通常是不好的。讓它們傳播到上層的handler,比如servlet或者EJB 容器通常更加合適。所有的Spring對象訪問異常都是 DataAccessException的子類,因而如果我們確實選擇了捕捉所有的Spring數據訪問異常,我們可以很容易做到這點。
注意如果我們確實需要從unchecked數據訪問異常中恢復,我們仍然可以這么做。我們可以編寫代碼僅僅處理可恢復的情況。例如,如果我們認為只有optimistic locking violation是可恢復的,我們可以在Spring的DAO中如下這么寫:
代碼:
try {
// do work
}
catch (OptimisticLockingFailureException ex) {
// I'm interested in this
}
如果Spring的數據訪問異常是checked的,我們需要編寫如下的代碼。注意我們還是可以選擇這么寫:
代碼:
try {
// do work
}
catch (OptimisticLockingFailureException ex) {
// I'm interested in this
}
catch (DataAccessException ex) {
// Fatal; just rethrow it
}
第一個例子的潛在缺陷是——編譯器不能強制處理可能的可恢復的異?!@對于第二個也是如此。因為我們被強制捕捉base exception (DataAccessException),編譯器不會強制對子類(OptimisticLockingFailureException)的檢查。因而編譯器可能強制我們編寫處理不可恢復問題的代碼,但是對于強制我們處理可恢復的問題并未有任何幫助。
Spring對于數據訪問異常的unchecked使用和許多——可能是大多數——成功的持久化框架是一致的。(確實,它部分是受到JDO的影響。) JDBC是少數幾個使用checked exception的數據訪問API之一。例如TopLink和JDO大量使用 unchecked exception。Gavin King現在相信Hibernate也應該選擇使用unchecked exception。
Spring的JDBC能夠用以下辦法幫助你:
你決不需要在使用JDBC時再編寫finally block。
總的來說你需要編寫的代碼更少了
你再也不需要挖掘你的RDBMS的文檔以找出它為錯誤的列名稱返回的某個罕見的錯誤代碼。你的程序不再依賴于RDBMS特有的錯誤處理代碼。
無論使用的是什么持久化技術,你都會發現容易實現DAO模式,讓業務代碼無需依賴于任何特定的數據訪問API。
在實踐中,我們發現所有這些都確實有助于生產力的提高和更少的bug。我過去常常厭惡編寫JDBC代碼;現在我發現我能夠集中精力于我要執行的SQL,而不是煩雜的JDBC資源管理。
如果需要的話Spring的JDBC抽象可以獨立使用——不強迫你把它們用作Spring的一部分。
O/R mapping 集成
當然你經常需要使用O/R mapping,而不是使用關系數據訪問。你總體的應用程序框架也必須支持它。因而提供了對Hibernate 2.x和 JDO的集成支持。它的數據訪問架構使得它能和任何底層的數據訪問技術集成。Spring和Hibernate集成得尤其好。
為什么你要使用Hibernate加Spring,而不是直接使用Hibernate?
Session 管理 Spring提供有效率的,簡單的以并且是安全的處理Hibernate Session。使用Hibernate的相關代碼為了效率和恰當的事務處理一般需要使用相同的Hibernate “Session”對象。Spring讓它容易透明地創建和綁定Session到當前的線程,要么使用聲明式,AOP的method interceptor方法,要么在Java代碼層面使用顯式的,“template”包裝類。因而 Spring解決了在Hibernate論壇上經常出現的用法問題。
資源管理 Spring的應用程序context能夠處理Hiberante SessionFactories的位置和配置,JDBC數據源和其他相關資源。這使得這些值易于管理和改變。
集成的事務管理 Spring讓你能夠把你的Hibernate代碼包裝起來,要么使用聲明式,AOP風格的method interceptor,要么在Java代碼層面顯式使用“template”包裝類。在兩種做法中,事務語義都為你處理了,并且在異常時也做好了恰當的事務處理(回滾,等)。如下面討論的,你還獲得了能夠使用和替換不同transaction manager,而不會讓你相關Hibernate代碼受到影響的能力。額外的,JDBC 相關的代碼能夠完全事務性的和Hibernate代碼集成。這對于處理沒有在Hibernate實現的功能很有用。
如上描述的異常包裝 Spring能夠包裝Hibernate異常,把它們從私有的,checked異常轉換為一套抽象的運行時異常。這使得你能夠僅僅在恰當的層面處理大部分不可恢復的持久化異常,而不影響樣板catch/throw,和異常聲明。你仍然能夠在任何你需要的地方捕捉和處理異常。記住 JDBC異常(包括DB特有的方言)也被轉換到相同的層次中,意味著你能在一致的編程模型中對JDBC執行相同的操作。
為了避免和廠商綁定 Hibernate是強大的,靈活的,開放源代碼并且免費,但是它仍然使用私有的API。給出了一些選擇,使用標準或者抽象API實現主要的程序功能通常是你想要的,當你需要因為功能,性能,或者其他考慮要轉換到使用其他實現時。
讓測試變簡單 Spring的Inversion of Control方法使得改變Hibernate的session factories,數據源, transaction manager的實現和位置很容易,如果需要的話還能改變mapper object的實現。這使得更加容易分離和測試持久化相關的代碼。
事務管理
抽象出一個數據訪問的API是不夠的;我們還需要考慮事務管理。JTA是顯而易見的選擇,但是它是一個直接用起來很笨重的API,因而許多J2EE開發者感到EJB CMT是對于事務管理唯一合理的選擇。
Spring提供了它自己對事務管理的抽象。Spring提供了這些:
通過類似于JdbcTemplate的回調模板編程管理事務,比起直接使用JTA要容易多了
類似于EJB CMT的聲明式事務管理,但是不需要EJB容器
Spring的事務抽象式唯一的,它不綁定到JTA或者任何其他事務管理技術。Spring使用事務策略的概念把程序代碼和底層的事務架構(例如JDBC)解藕。
為什么你要關心這些?JTA不是所有事務管理的最好答案嗎?如果你正在編寫僅僅使用一個數據庫的程序,你不需要JTA的復雜度。你不關心XA事務或者兩階段提交。你甚至不需要提供這些東西的高端應用服務器。但是另一方面,你不會希望在需要和多個數據源打交道的時候重寫你的代碼。
假定你決定通過直接使用JDBC或者Hibernate的事務以避免JTA帶來的額外負擔。一旦你需要處理多個數據源,你必須剝開所有的事務管理代碼并且使用JTA事務來替代。這不是非常有吸引力的并且導致大部分J2EE程序員,包括我自己,推薦只使用全局JTA事務。然而使用Spring事務抽象,你只需要重新配置Spring讓它使用JTA,而不是JDBC或者Hibernate的事務策略,就一切OK了。這是一個配置上的改變,而不是代碼的改動。因而,Spring使得你能夠自由縮放應用。
AOP
最近在應用AOP來解決企業關注點方面大家有了很大的興趣,例如事務管理,這些都是EJB所要解決的。
Spring的AOP支持的首要目標是要給POJOs提供J2EE服務。這類似于JBoss 4的目標,Spring AOP由它能夠在應用服務器之間移植的優勢,因而沒有綁死在廠商身上的風險。它既可以在web或者EJB容器中使用,也能夠在WebLogic,Tomcat,JBoss,Resin, Jetty,Orion和許多其他應用服務器和web容器上使用。
Spring AOP支持method interception。所支持關鍵的AOP概念包括:
Interception:自定義行為能夠在對接口和類的調用之前和之后插入。這類似于AspectJ術語中類似的“around advice”。
Introduction:指定advice會導致對象實現額外的接口。這混亂了繼承。
靜態和動態的pointcuts:在interception發生的程序執行處指定points。靜態pointcuts concern函數簽名;動態 pointcuts也可以在point被求值的地方考慮函數的參數。Pointcuts獨立interceptors單獨定義,使得標準 interceptor可以應用于不同應用程序和代碼上下文。
Spring既支持有狀態(一個advised對象一個實例)也支持無狀態的interceptors(所有advice使用一個實例)。
Spring不支持field interception。這是一個經過深思熟慮的設計決定。我總是感覺field interception違反了封裝。我比較傾向于把AOP作為補全物,而不是與OOP沖突的東西。如果在5年或者10年后,我們在AOP學習曲線上走得更遠了并且覺得應該在程序設計的桌面上給AOP一個位置,我不會驚訝的。(然而在那個時候基于語言的解決方案例如AspectJ可能比它們今天看來更加具有吸引力。)
Spring使用動態代理實現AOP(其中存在一個接口)或者在運行時使用CGLIB生成字節碼(這使得能夠代理類)。兩種方法都能夠在任何應用服務器中使用。
Spring是第一個實現AOP Alliance interfaces的AOP 框架(www.sourceforge.net/projects/aopalliance)。這些是定義在不同AOP框架中能夠互操作interceptors的嘗試。
在TheServerSide和其他地方有一個正在進行但是不是那么引人注目的爭論,就是這種interception是不是“true AOP”。我倒不怎么在意它叫什么;僅僅需要知道它是否在實踐中有用就好了。我也樂于稱它為“declarative middleware”(聲明式中間件)。把 Spring AOP認做簡單,輕量級的無狀態beans的替代物,這樣就不需要monolithic EJB容器了,而這些僅僅是讓你能夠構建有你需要的服務的容器。我不推薦advising任何一個POJO,對local SLSBs的類比有助于你理解推薦的粒度。(然而,與EJB不同的是,在恰當但是少見的情況下,你可以自由地把Spring的AOP應用到粒度更好的對象上。)
因為Spring在實例上advises 對象,而不是在class loader層面上,使用有不同advice的同一個類的多個實例是可能的,或者與advised實例一道使用unadvised 實例。
可能Spring AOP最常見的應用是聲明式事務管理。這是基于前面描述的TansactionTemplate抽象上的,并且可以給任何POJO提供聲明式事務管理。取決于事務策略,底層的機制可以是JTA,JDBC,Hibernate或者任何其他提供事務管理的API。
Spring的聲明式事務管理類似于EJB CMT,在以下方面有些不同:
事務管理能夠應用于任何POJO。我們推薦業務對象實現接口,但是這只是一個好的編程習慣的問題,而不是由框架強制的。
通過使用Spring的事務API能夠在事務性POJO中實現編程回調。我們為此提供靜態的方法,使用ThreadLoacal變量,因而你不需要傳播諸如EJBContext這樣的context對象來確?;貪L。
你可以聲明式地定義“回滾規則”。EJB不會在未捕捉程序異常的時候自動回滾(僅僅在unchecked exceptions和其他 Throwables的時候),應用程序開發者經常需要在任何異常發生時回滾。Spring事務管理讓你能夠聲明式地指定什么異常什么子類能夠導致自動回滾。缺省的行為和EJB是一致的,但是你能夠在checked和unchecked異常時自動回滾。這個在最少化自編程回調代碼方面有很大好處,而回調依賴于Spring的事務API(因為EJB的編程回調時在EJBContext中完成的)。
事務管理不綁定于JTA。如前面解釋過的,Spring的事務管理能夠在不同事務策略中使用。
當然還可以使用Spring AOP實現程序特有的aspects。取決于你對AOP概念的接受程度,決定你是否選擇這么做,而不是Spring的能力,但是它確實非常有用。我們所見過的成功例子包括:
自定義的security interception,當安全檢查的復雜度超出了J2EE安全架構的能力的時候
在開發中使用的調試和profiling aspects
發送email通知管理員用戶不尋常的舉動的Interceptors
程序自定的aspects能夠成為消除需要許多函數的樣板代碼的有利武器。
Spring AOP透明地與Spring BeanFactory概念集成。包含一個來自Spring BeanFactory對象地代碼不需要知道它是還是不是advised。和任何對象一樣,契約實在接口和對象實現中定義的。
下面的XML片斷展示了如何定義一個AOP代理:
代碼:
<bean id="myTest"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>org.springframework.beans.ITestBean</value>
</property>
<property name="interceptorNames">
<list>
<value>txInterceptor</value>
<value>target</value>
</list>
</property>
</bean>
注意bean類的定義總是AOP框架的ProxyFactoryBean,雖然bean的類型在引用中使用或者由BeanFactory的getBean ()方法返回時依賴的是代理接口。(多個代理方法是被支持的。)ProxyFactoryBean的“interceptorNames”屬性需要一個字符串列表。(因為如果代理是一個“prototype”而不是singleton,有狀態interceptors可能需要創建新的實例,所以必須使用 Bean的名字而不是bean的引用。)列表中的名字可以是interceptor或者pointcuts(interceptors和有關它們合適被使用的信息)。列表中的“target”值自動創建一個“invoker interceptor”封裝target對象。實現代理接口的是在 factory中的bean的名字。這個例子中的myTest可以和其他bean factory中的bean一樣使用。例如,其他對象可以使用< ref>元素引用它而且這些引用是由Spring IoC設置的。
還可以不用BeanFactory,編程構建AOP代理,雖然這個很少用得上:
代碼:
TestBean target = new TestBean();
DebugInterceptor di = new DebugInterceptor();
MyInterceptor mi = new MyInterceptor();
ProxyFactory factory = new ProxyFactory(target);
factory.addInterceptor(0, di);
factory.addInterceptor(1, mi);
// An "invoker interceptor" is automatically added to wrap the target
ITestBean tb = (ITestBean) factory.getProxy();
我們相信最好把程序裝配從Java代碼中移出來,而AOP也不例外。
Spring在它的AOP能力方面的直接競爭者是Jon Tirsen的Nanning Aspects(http://nanning.codehaus.org)。
我覺得AOP作為EJB的替代無提供企業服務這個用法方面的進步是重要的。隨著時間,這將成為Spring很重要的關注點。
MVC web 框架
Spring包括一個強大而且高度可配置的MVC web 框架。
Spring的MVC model類似于Struts。在多線程服務對象這點上,Spring的Controller類似于Struts Action,只有一個實例處理所有客戶的請求。然而,我們相信Spring的MVC比起Struts有很多優點,例如:
Spring在controllers,JavaBean,models和views提供了一個非常清晰的劃分。
Spring的MVC是非常靈活的。不像Struts,它強制你的Action和Form對象進入固化的層次之中(因而你迫使你使用Java的實體繼承),Spring MVC完全是基于接口的。而且,通過插入你自己的接口幾乎Spring MVC 框架的所有部分都是可配置的。當然我們也提供了方便的類作為實現選擇。
Spring MVC是真正的view無關的。你不會被強制使用JSP,如果你不想那么做的話。你可以使用Velocity,XSLT或其他view技術。如果你想要使用自定義的view機制——例如,你自己的模板語言——你可以簡單實現Spring的View接口并且把它集成進來。
和其他對象一樣,Spring的Controllers是通過IoC配置的。著使得它們易于測試,并且完美地和其他由Spring管理的對象集成。
Web層變成了業務對象層之上的薄薄一層。這鼓勵了好的習慣。Struts和其他專門的web框架讓你去實現你自己的業務對象;Spring提供了你應用程序所有層的集成。
如在Struts 1.1中所見的,你可以有和你在Spring MVC 應用程序中所需要的一樣多的dispatcher servlets。
下面的例子展示了一個簡單的Spring Controller如何能夠訪問定義在應用程序context中的業務對象。這個controller在它的handleRequest()方法中執行了Google搜索:
代碼:
public class GoogleSearchController
implements Controller {
private IGoogleSearchPort google;
private String googleKey;
public void setGoogle(IGoogleSearchPort google) {
this.google = google;
}
public void setGoogleKey(String googleKey) {
this.googleKey = googleKey;
}
public ModelAndView handleRequest(
HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String query = request.getParameter("query");
GoogleSearchResult result =
// Google property definitions omitted...
// Use google business object
google.doGoogleSearch(this.googleKey, query,
start, maxResults, filter, restrict,
safeSearch, lr, ie, oe);
return new ModelAndView("googleResults", "result", result);
}
}
這段代碼使用的prototype中,IGoogleSearchPort是一個GLUE web services代理,由 Spring FActoryBean返回。然而,Spring把controller從底層web service庫中分離出來。接口可以使用普通的 Java對象,test stub,mock對象或者如下面要討論的EJB代理實現。這個contorller不包括資源查找;除了支持它的web交互的必要代碼之外沒有別的什么了。
Spring還提供了數據綁定,forms,wizards和更復雜的工作流的支持。
對Spring MVC 框架的優秀簡介是Thomas Risberg的Spring MVC 教程(http://www.springframework.org/docs/MVC-step-by-step/Spring-MVC-step-by-step.html)。還可以參見“Web MVC with the Spring Framework”(http://www.springframework.org/docs/web_mvc.html)。
如果你樂于使用你鐘情的MVC框架,Spring的分層架構使得你能夠使用Spring的其他部分而不用MVC層。我們有使用Spring做中間層管理和數據訪問,但是在web層使用Struts,WebWork或者Tapestry的用戶。
實現EJB
如果你選擇使用EJB,Spring能在EJB實現和客戶端訪問EJB兩方面都提供很大的好處。
對業務邏輯進行重構,把它從EJB facades中取出到POJO已經得到了廣泛的認同。(不講別的,這使得業務邏輯更容易單元測試,因為EJB嚴重依賴于容器而難于分離測試。)Spring為session bean和message driver bean提供了方便的超類,使得通過自動載入基于包含在EJB Jar文件中的XML文檔BeanFactory讓這變得很容易。
這意味著stateless session EJB可以這么獲得和使用所需對象:
代碼:
import org.springframework.ejb.support.AbstractStatelessSessionBean;
public class MyEJB extends AbstractStatelessSessionBean
implements MyBusinessInterface {
private MyPOJO myPOJO;
protected void onEjbCreate() {
this.myPOJO = getBeanFactory().getBean("myPOJO");
}
public void myBusinessMethod() {
this.myPOJO.invokeMethod();
}
}
假定MyPOJO是一個接口,它的實現類——以及任何它需要的配置,注入基本的屬性和更多的合作者——在XML bean factory 定義中隱藏。
我們通過在ejb-jar.xmldeployment descriptor中名為ejb/BeanFactoryPath的環境變量定義告訴Spring去哪兒裝載XML文檔。如下:
代碼:
<session>
<ejb-name>myComponent</ejb-name>
<local-home>com.test.ejb.myEjbBeanLocalHome</local-home>
<local>com.mycom.MyComponentLocal</local>
<ejb-class>com.mycom.MyComponentEJB</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
<env-entry>
<env-entry-name>ejb/BeanFactoryPath</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>/myComponent-ejb-beans.xml</env-entry-value></env-entry>
</env-entry>
</session>
myComponent-ejb-beans.xml 文件將會從classpath裝載:在本例中,是EJB Jar文件的根目錄。每個EJB都能指定自己的XML文檔,因而這個機制能在每個EJB Jar文件中使用多次。
Spring 的超類實現了EJB中諸如setSessionContext()和ejbCreate()的生命周期管理的方法,讓應用程序開發者只需選擇是否實現Spring的onEjbCreate()方法。
使用EJB
Spring還讓實現EJB變得更加容易。許多EJB程序使用Service Locator和Business Delegate模式。這些比在客戶代碼中遍布JNDI查找強多了,但是它們常見的實現方式有顯著的缺點,例如:
使用EJB的典型代碼依賴Service Locator或者Business Delegate singletons,使得測試難于進行。
在Service Locator模式沒有使用Business Delegate的情況下,程序代碼還要在EJB home中調用create()方法,并且處理可能導致的異常。因而仍然綁定在EJB API身上,忍受著EJB 編程模型的復雜度。
實現Business Delegate模式通常導致顯著的代碼重復,其中我們必須編寫大量僅僅是調用EJB同等方法的方法。
基于這些和其他原因,傳統的EJB訪問,如在Sun Adventure Builder和OTN J2EE Virtual Shopping Mall中展示的那樣,會降低生產率并且帶來顯著的復雜度。
Spring通過引入codeless business delegate前進了一步。有了Spring,你不再需要再編寫另一個 Service Locator,另一個JNDI查找,或者在硬編碼的Business Delegate中重復代碼,除非你肯定這增加了價值。
例如,假定我們有使用local EJB的web controller。我們將遵循最佳實踐,使用 EJB Business Methods Interface模式,EJB的local interface extend非EJB專有的業務方法接口。(這么做的主要的一個原因是確保在本地接口和bean實現類中方法簽名的自動同步。)讓我們調用這個業務方法接口MyComponent。當然我們還需要實現local home接口并且提供實現SessionBean和MyComponent業務方法的bean的實現類。
用了Spring EJB 訪問,我們把我們的web層controller和EJB實現掛接上所需要進行的Java編碼僅僅是在我們的controller中暴露一個類型MyComponent的setter方法。這將如下保存作為實例變量的引用:
代碼:
private MyComponent myComponent;
public void setMyComponent(MyComponent myComponent) {
this.myComponent = myComponent;
}
我們隨后在任何業務方法中使用這個實例變量。
Spring自動完稱剩下的工作,通過像這樣的XML bean定義。LocalStatelessSessionProxyFactoryBean是一個可以用于任何EJB的通用factory bean。它創建的對象能夠自動被Spring轉型為MyComponent類型。
代碼:
<bean id="myComponent"
class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">
<property name="jndiName"><value>myComponent</value></property>
<property name="businessInterface"><value>com.mycom.MyComponent</value></property>
</bean>
<bean id="myController"
class = "com.mycom.myController"
>
<property name="myComponent"><ref bean="myComponent"/></property>
</bean>
在幕后有許多魔法般的事情發生,Spring AOP framework的殷勤,雖然不強迫你使用AOP的概念享受這些結果。 “myComponent”bean定義為EJB創建一個代理,它實現了業務方法的接口。EJB local home在啟動的時候被緩存,因而只需要一次JNDI查找。每次EJB被調用的時候,代理調用local EJB中的create()方法并且調用EJB中對應的業務方法。
myController bean定義為這個代理設置controller類的myController屬性。
這個EJB訪問機制極大簡化了應用程序的代碼:
Web層的代碼不依賴于EJB的使用。如果你想要使用POJO,mock object或者其他test stub替代EJB引用,我們可以簡單地改動一下myComponent bean定義而不影響一行Java代碼
我們還不需要寫一行JNDI查找或者其他EJB plumbing code。
在實際程序中的性能測試和經驗標明這種方法(包括對目標EJB的反射調用)的性能影響是很小的,在典型的應用中檢測不出。記住無論如何我們都不希望使用fine-grained的EJB調用,因為會有有關應用服務器上的EJB的底層架構方面的代價。
我們可以把相同方法應用于遠程EJB,通過類似 org.springframework.ejb.access.SimpleRemoteStatelessSessionProxyFactoryBean factory bean 的方法。然而我們無法隱藏遠程EJB的業務方法接口中的RemoteException。
測試
如你可能已經注意到的,我和其他Spring開發這是全面單元測試重要性的堅定支持者。我們相信框架被徹底單元測試過的是非常重要的,而且我們框架設計的主要目標是讓建立在框架之上的程序易于單元測試。
Spring自身有一個極好的單元測試包。我們的1.0 M1的單元測試覆蓋率是75%,而且我們希望在1.0 RC1的時候能夠達到80%的單元測試覆蓋率。我們發現在這個項目中測試優先的開發帶來的好處是實實在在的。例如,它使得作為國際化分布式開發團隊的工作極端有效率,而且用戶評論 CVS snapshots趨向于穩定和使用安全。
因為以下理由,我們相信用Spring構建的應用程序是非常易于測試的:
IoC推動了單元測試
應用程序不包括直接使用注入JNDI的J2EE服務的plumbing code,這些代碼一般讓測試難于進行
Spring bean factories和contexts能夠在容器外設置
在容器外可以設置Spring bean factory的能力提供了對開發過程有趣的可選項。在幾個使用Spring的web應用中,工作是從定義業務接口和在web容器外集成測試開始的。在業務功能已經足夠完整之后,web接口不過是添加在其上的薄薄一層。
誰在使用Spring
雖然相對來說Spring還是一個新的項目,但是我們已經有了一個令人印象深刻并且不斷增長的用戶群。它們已經有許多產品使用著Spring。用戶包括一個主要的全球投資銀行(做大型項目的),一些知名的網絡公司,幾個web開發顧問機構,衛生保健公司,以及學院機構。
許多用戶完整地使用Spring,但是一些只單獨使用一些組件。例如,大量用戶使用我們地JDBC或者其他數據訪問功能。
Roadmap
在今年晚些時候我們主要要做的是讓Spring發布release 1.0。然而,我們還有一些更長遠的目標。
為1.0 final規劃地主要改進式源代碼級地元數據支持,它主要用于(但不局限于)AOP框架。這將使得C#風格的attribute驅動的事務管理,并且讓聲明式企業服務在典型應用情況下非常容易配置。Attribute支持將會在Spring的1.0 final release支持中加入,并且設計的是在發布的那個時候能與JSR-175集成。
1.0之后,一些可能的改進地方包括:
通過對我們的JDBC和事務支持的一個相當抽象來支持JMS
支持bean factories的動態重配置
提供web services的能力
IDE和其他工具支持
作為一個敏捷項目,我們主要是受到用戶需求的驅動。因而我們不會開發沒有一個用戶需要的特性,并且我們會仔細傾聽來自用戶群的聲音。
總結
Spring是一個解決了許多在J2EE開發中常見的問題的強大框架。
Spring提供了管理業務對象的一致方法并且鼓勵了注入對接口編程而不是對類編程的良好習慣。Spring的架構基礎是基于使用JavaBean屬性的 Inversion of Control容器。然而,這僅僅是完整圖景中的一部分:Spring在使用IoC容器作為構建完關注所有架構層的完整解決方案方面是獨一無二的。
Spring提供了唯一的數據訪問抽象,包括簡單和有效率的JDBC框架,極大的改進了效率并且減少了可能的錯誤。Spring的數據訪問架構還集成了Hibernate和其他O/R mapping解決方案。
Spring還提供了唯一的事務管理抽象,它能夠在各種底層事務管理技術,例如JTA或者JDBC紙上提供一個一致的編程模型。
Spring提供了一個用標準Java語言編寫的AOP框架,它給POJOs提供了聲明式的事務管理和其他企業事務——如果你需要——還能實現你自己的aspects。這個框架足夠強大,使得應用程序能夠拋開EJB的復雜性,同時享受著和傳統EJB相關的關鍵服務。
Spring還提供了可以和總體的IoC容器集成的強大而靈活的MVC web框架。
更多信息
參見以下資源獲得關于Spring的更多信息:
Expert One-on-One J2EE Design and Development(Rod Johnson,Wrox,2002)。雖然Spring在書出版之后已經極大地進步和改進了,它仍然是理解Spring動機的極佳途徑。
Spring的主頁:http://www.springframework.org。這里包括Javadoc和幾個教程。
在Sourceforge上的論壇和下載
Spring用戶和Spring開發者的郵件列表
我們正在盡我們可能去改進Spring的文檔和示例。我們還為在信件和郵件列表中極好的回復率自豪。我們希望你能快速融入我們的社區!
關于作者
Rod Johnson 作為Java開發者和架構師已經有了7年的經驗了并且在J2EE平臺出現之初就在其上進行開發了。他是《Expert One- on-One J2EE Design and Development》(Wrox,2002)的作者并且貢獻了其他好幾本關于J2EE的書。他當前正在為Wiley撰寫另外一本有關J2EE架構的書。Rod在兩個Java標準委員會服務并且經常師大會發言人?,F在他在UK做一個咨詢顧問。
posted @
2009-04-07 23:30 lanxin1020 閱讀(136) |
評論 (0) |
編輯 收藏
Spring的AbstractApplicationContext是ApplicationContext抽象實現類,該抽象類的refresh()方法定義了Spring容器在加載配置文件后的各項處理過程,這些處理過程清晰刻畫了Spring容器啟動時所執行的各項操作。下面,我們來看一下refresh()內部定義了哪些執行邏輯:
1.初始化BeanFactory:根據配置文件實例化BeanFactory,getBeanFactory()方法由具體子類實現。在這一步里,Spring將配置文件的信息裝入到容器的Bean定義注冊表(BeanDefinitionRegistry)中,但此時Bean還未初始化;
2.調用工廠后處理器:根據反射機制從BeanDefinitionRegistry中找出所有BeanFactoryPostProcessor類型的Bean,并調用其postProcessBeanFactory()接口方法;
3.注冊Bean后處理器:根據反射機制從BeanDefinitionRegistry中找出所有BeanPostProcessor類型的Bean,并將它們注冊到容器Bean后處理器的注冊表中;
4.初始化消息源:初始化容器的國際化信息資源;
5.初始化應用上下文事件廣播器;
6.初始化其他特殊的Bean:這是一個鉤子方法,子類可以借助這個鉤子方法執行一些特殊的操作:如AbstractRefreshableWebApplicationContext就使用該鉤子方法執行初始化ThemeSource的操作;
7.注冊事件監聽器;
8.初始化singleton的Bean:實例化所有singleton的Bean,并將它們放入Spring容器的緩存中;
9.發布上下文刷新事件:創建上下文刷新事件,事件廣播器負責將些事件廣播到每個注冊的事件監聽器中。
在第3.4節中,我們觀摩了Bean從創建到銷毀的生命歷程,這些過程都可以在上面的過程中找到對應的步驟。Spring協調多個組件共同完成這個復雜的工程流程,圖5-1描述了Spring容器從加載配置文件到創建出一個完整Bean的作業流程以及參與的角色。
 |
圖5-1 IoC的流水線 |
1.ResourceLoader從存儲介質中加載Spring配置文件,并使用Resource表示這個配置文件的資源;
2.BeanDefinitionReader讀取Resource所指向的配置文件資源,然后解析配置文件。配置文件中每一個<bean>解析成一個BeanDefinition對象,并保存到BeanDefinitionRegistry中;
3.容器掃描BeanDefinitionRegistry中的BeanDefinition,使用Java的反射機制自動識別出Bean工廠后處理器(實現BeanFactoryPostProcessor接口)的Bean,然后調用這些Bean工廠后處理器對BeanDefinitionRegistry中的BeanDefinition進行加工處理。主要完成以下兩項工作:
1)對使用到占位符的<bean>元素標簽進行解析,得到最終的配置值,這意味對一些半成品式的BeanDefinition對象進行加工處理并得到成品的BeanDefinition對象;
2)對BeanDefinitionRegistry中的BeanDefinition進行掃描,通過Java反射機制找出所有屬性編輯器的Bean(實現java.beans.PropertyEditor接口的Bean),并自動將它們注冊到Spring容器的屬性編輯器注冊表中(PropertyEditorRegistry);
4.Spring容器從BeanDefinitionRegistry中取出加工后的BeanDefinition,并調用InstantiationStrategy著手進行Bean實例化的工作;
5.在實例化Bean時,Spring容器使用BeanWrapper對Bean進行封裝,BeanWrapper提供了很多以Java反射機制操作Bean的方法,它將結合該Bean的BeanDefinition以及容器中屬性編輯器,完成Bean屬性的設置工作;
6.利用容器中注冊的Bean后處理器(實現BeanPostProcessor接口的Bean)對已經完成屬性設置工作的Bean進行后續加工,直接裝配出一個準備就緒的Bean。
Spring容器確實堪稱一部設計精密的機器,其內部擁有眾多的組件和裝置。Spring的高明之處在于,它使用眾多接口描繪出了所有裝置的藍圖,構建好Spring的骨架,繼而通過繼承體系層層推演,不斷豐富,最終讓Spring成為有血有肉的完整的框架。所以查看Spring框架的源碼時,有兩條清晰可見的脈絡:
1)接口層描述了容器的重要組件及組件間的協作關系;
2)繼承體系逐步實現組件的各項功能。
接口層清晰地勾勒出Spring框架的高層功能,框架脈絡呼之欲出。有了接口層抽象的描述后,不但Spring自己可以提供具體的實現,任何第三方組織也可以提供不同實現, 可以說Spring完善的接口層使框架的擴展性得到了很好的保證??v向繼承體系的逐步擴展,分步驟地實現框架的功能,這種實現方案保證了框架功能不會堆積在某些類的身上,造成過重的代碼邏輯負載,框架的復雜度被完美地分解開了。
Spring組件按其所承擔的角色可以劃分為兩類:
1)物料組件:Resource、BeanDefinition、PropertyEditor以及最終的Bean等,它們是加工流程中被加工、被消費的組件,就像流水線上被加工的物料;
2)加工設備組件:ResourceLoader、BeanDefinitionReader、BeanFactoryPostProcessor、InstantiationStrategy以及BeanWrapper等組件像是流水線上不同環節的加工設備,對物料組件進行加工處理。
我們在第3章中已經介紹了Resource和ResourceLoader這兩個組件。在本章中,我們將對其他的組件進行講解。
posted @
2009-04-07 23:06 lanxin1020 閱讀(284) |
評論 (0) |
編輯 收藏
摘要: 單例模式是最簡單的設計模式之一,但是對于Java的開發者來說,它卻有很多缺陷。在本月的專欄中,David Geary探討了單例模式以及在面對多線程(multithreading)、類裝載器(classloaders)和序列化(serialization)時如何處理這些缺陷。
單例模式適合于一個類只有一個實例的情況,比如窗口管理器,打印緩沖池和文件系統,它們都是原型的例子。典型的情況是,那些...
閱讀全文
posted @
2009-04-06 18:35 lanxin1020 閱讀(155) |
評論 (0) |
編輯 收藏