數據庫查詢性能的提高也是涉及到開發中的各個階段,在開發中選用正確的查詢方法無疑是最基礎也最簡單的。java
使用正確的SQL語句能夠在很大程度上提升系統的查詢性能。得到一樣數據而採用不一樣方式的SQL語句在性能上的差距多是十分巨大的。mysql
因爲Hibernate是對JDBC的封裝,SQL語句的產生都是動態由Hibernate自動完成的。Hibernate產生SQL語句的方式有兩種:一種是經過開發人員編寫的HQL語句來生成,另外一種是依據開發人員對關聯對象的訪問來自動生成相應的SQL語句。程序員
至於使用什麼樣的SQL語句能夠得到更好的性能要依據數據庫的結構以及所要獲取數據的具體狀況來進行處理。在肯定了所要執行的SQL語句後,能夠經過如下三個方面來影響Hibernate所生成的SQL語句:web
● HQL語句的書寫方法。正則表達式
● 查詢時所使用的查詢方法。spring
● 對象關聯時所使用的抓取策略。sql
在前面已經介紹過,執行數據查詢功能的基本方法有兩種:一種是獲得單個持久化對象的get()方法和load()方法,另外一種是Query對象的list()方法和iterator()方法。在開發中應該依據不一樣的狀況選用正確的方法。數據庫
get()方法和load()方法的區別在於對二級緩存的使用上。load()方法會使用二級緩存,而get()方法在一級緩存沒有找到的狀況下會直接查詢數據庫,不會去二級緩存中查找。在使用中,對使用了二級緩存的對象進行查詢時最好使用load()方法,以充分利用二級緩存來提升檢索的效率。express
list()方法和iterator()方法之間的區別能夠從如下幾個方面來進行比較。
● 執行的查詢不一樣
list()方法在執行時,是直接運行查詢結果所須要的查詢語句,而iterator()方法則是先執行獲得對象ID的查詢,而後再根據每一個ID值去取得所要查詢的對象。所以,對於list()方式的查詢一般只會執行一個SQL語句,而對於iterator()方法的查詢則可能須要執行N+1條SQL語句(N爲結果集中的記錄數)。
iterator()方法只是可能執行N+1條數據,具體執行SQL語句的數量取決於緩存的狀況以及對結果集的訪問狀況。
● 緩存的使用
list()方法只能使用二級緩存中的查詢緩存,而沒法使用二級緩存對單個對象的緩存(可是會把查詢出的對象放入二級緩存中)。因此,除非重複執行相同的查詢操做,不然沒法利用緩存的機制來提升查詢的效率。
iterator()方法則能夠充分利用二級緩存,在根據ID檢索對象的時候會首先到緩存中查找,只有在找不到的狀況下才會執行相應的查詢語句。因此,緩存中對象的存在與否會影響到SQL語句的執行數量。
● 對於結果集的處理方法不一樣
list()方法會一次得到全部的結果集對象,並且它會依據查詢的結果初始化全部的結果集對象。這在結果集很是大的時候必然會佔據很是多的內存,甚至會形成內存溢出狀況的發生。
iterator()方法在執行時不會一次初始化全部的對象,而是根據對結果集的訪問狀況來初始化對象。所以在訪問中能夠控制緩存中對象的數量,以免佔用過多緩存,致使內存溢出狀況的發生。使用iterator()方法的另一個好處是,若是隻須要結果集中的部分記錄,那麼沒有被用到的結果對象根本不會被初始化。因此,對結果集的訪問狀況也是調用iterator()方法時執行數據庫SQL語句多少的一個因素。
因此,在使用Query對象執行數據查詢時應該從以上幾個方面去考慮使用何種方法來執行數據庫的查詢操做。
所謂抓取策略(fetching strategy)是指當應用程序須要利用關聯關係進行對象獲取的時候,Hibernate獲取關聯對象的策略。抓取策略能夠在O/R映射的元數據中聲明,也能夠在特定的HQL或條件查詢中聲明。
Hibernate 3定義瞭如下幾種抓取策略。
● 鏈接抓取(Join fetching)
鏈接抓取是指Hibernate在得到關聯對象時會在SELECT語句中使用外鏈接的方式來得到關聯對象。
● 查詢抓取(Select fetching)
查詢抓取是指Hibernate經過另一條SELECT語句來抓取當前對象的關聯對象的方式。這也是經過外鍵的方式來執行數據庫的查詢。與鏈接抓取的區別在於,一般狀況下這個SELECT語句不是當即執行的,而是在訪問到關聯對象的時候纔會執行。
● 子查詢抓取(Subselect fetching)
子查詢抓取也是指Hibernate經過另一條SELECT語句來抓取當前對象的關聯對象的方式。與查詢抓取的區別在於它所採用的SELECT語句的方式爲子查詢,而不是經過外鏈接。
● 批量抓取(Batch fetching)
批量抓取是對查詢抓取的優化,它會依據主鍵或者外鍵的列表來經過單條SELECT語句實現管理對象的批量抓取。
以上介紹的是Hibernate 3所提供的抓取策略,也就是抓取關聯對象的手段。爲了提高系統的性能,在抓取關聯對象的時機上,還有如下一些選擇。
● 當即抓取(Immediate fetching)
當即抓取是指宿主對象被加載時,它所關聯的對象也會被當即加載。
● 延遲集合抓取(Lazy collection fetching)
延遲集合抓取是指在加載宿主對象時,並不當即加載它所關聯的對象,而是到應用程序訪問關聯對象的時候才抓取關聯對象。這是集合關聯對象的默認行爲。
● 延遲代理抓取(Lazy proxy fetching)
延遲代理抓取是指在返回單值關聯對象的狀況下,並不在對其進行get操做時抓取,而是直到調用其某個方法的時候纔會抓取這個對象。
● 延遲屬性加載(Lazy attribute fetching)
延遲屬性加載是指在關聯對象被訪問的時候才進行關聯對象的抓取。
介紹了Hibernate所提供的關聯對象的抓取方法和抓取時機,這兩個方面的因素都會影響Hibernate的抓取行爲,最重要的是要清楚這兩方面的影響是不一樣的,不要將這兩個因素混淆,在開發中要結合實際狀況選用正確的抓取策略和合適的抓取時機。
抓取時機的選擇
在Hibernate 3中,對於集合類型的關聯在默認狀況下會使用延遲集合加載的抓取時機,而對於返回單值類型的關聯在默認狀況下會使用延遲代理抓取的抓取時機。
對於當即抓取在開發中不多被用到,由於這極可能會形成沒必要要的數據庫操做,從而影響系統的性能。當宿主對象和關聯對象老是被同時訪問的時候纔有可能會用到這種抓取時機。另外,使用當即鏈接抓取能夠經過外鏈接來減小查詢SQL語句的數量,因此,也會在某些特殊的狀況下使用。
然而,延遲加載又會面臨另一個問題,若是在Session關閉前關聯對象沒有被實例化,那麼在訪問關聯對象的時候就會拋出異常。處理的方法就是在事務提交以前就完成對關聯對象的訪問。
因此,在一般狀況下都會使用延遲的方式來抓取關聯的對象。由於每一個當即抓取都會致使關聯對象的當即實例化,太多的當即抓取關聯會致使大量的對象被實例化,從而佔用過多的內存資源。
抓取策略的選取
對於抓取策略的選取將影響到抓取關聯對象的方式,也就是抓取關聯對象時所執行的SQL語句。這就要根據實際的業務需求、數據的數量以及數據庫的結構來進行選擇了。
在這裏須要注意的是,一般狀況下都會在執行查詢的時候針對每一個查詢來指定對其合適的抓取策略。指定抓取策略的方法以下所示:
User user = (User) session.createCriteria(User.class)
.setFetchMode("permissions", FetchMode.JOIN)
.add( Restrictions.idEq(userId) )
.uniqueResult();
本文介紹了查詢性能提高的方法,關鍵是如何經過優化SQL語句來提高系統的查詢性能。查詢方法和抓取策略的影響也是經過執行查詢方式和SQL語句的多少來改變系統的性能的。這些都屬於開發人員所應該掌握的基本技能,避免因爲開發不當而致使系統性能的低下。
在性能調整中,除了前面介紹的執行SQL語句的因素外,對於緩存的使用也會影響系統的性能。一般來講,緩存的使用會增長系統查詢的性能,而下降系統增長、修改和刪除操做的性能(由於要進行緩存的同步處理)。因此,開發人員應該可以正確地使用有效的緩存來提升數據查詢的性能,而要避免濫用緩存而致使的系統性能變低。在採用緩存的時候也應該注意調整本身的檢索策略和查詢方法,這三者配合起來才能夠達到最優的性能。
另外,事務的使用策略也會影響到系統的性能。選取正確的事務隔離級別以及使用正確的鎖機制來控制數據的併發訪問都會影響到系統的性能。
posted @ 2009-07-19 21:36 jadmin 閱讀(1) 評論(0) 編輯
Hibernate是對JDBC的輕量級封裝,所以在不少狀況下Hibernate的性能比直接使用JDBC存取數據庫要低。然而,經過正確的方法和策略,在使用Hibernate的時候仍是能夠很是接近直接使用JDBC時的效率的,而且,在有些狀況下還有可能高於使用JDBC時的執行效率。
在進行Hibernate性能優化時,須要從如下幾個方面進行考慮:
● 數據庫設計調整。
● HQL優化。
● API的正確使用(如根據不一樣的業務類型選用不一樣的集合及查詢API)。
● 主配置參數(日誌、查詢緩存、fetch_size、batch_size等)。
● 映射文件優化(ID生成策略、二級緩存、延遲加載、關聯優化)。
● 一級緩存的管理。
● 針對二級緩存,還有許多特有的策略。
● 事務控制策略。
數據的查詢性能每每是影響一個應用系統性能的主要因素。對查詢性能的影響會涉及到系統軟件開發的各個階段,例如,良好的設計、正確的查詢方法、適當的緩存都有利於系統性能的提高。
系統性能的提高設計到系統中的各個方面,是一個相互平衡的過程,須要在應用的各個階段都要考慮。而且在開發、運行的過程當中要不斷地調整和優化才能逐步提高系統的性能。
posted @ 2009-07-19 21:30 jadmin 閱讀(1) 評論(0) 編輯
在前面介紹了Hibernate的緩存技術以及基本的用法,在這裏就具體的Hibernate所提供的查詢方法與Hibernate緩存之間的關係作一個簡單的總結。
在開發中,一般是經過兩種方式來執行對數據庫的查詢操做的。一種方式是經過ID來得到單獨的Java對象,另外一種方式是經過HQL語句來執行對數據庫的查詢操做。下面就分別結合這兩種查詢方式來講明一下緩存的做用。
經過ID來得到Java對象能夠直接使用Session對象的load()或者get()方法,這兩種方式的區別就在於對緩存的使用上。
● load()方法
在使用了二級緩存的狀況下,使用load()方法會在二級緩存中查找指定的對象是否存在。
在執行load()方法時,Hibernate首先從當前Session的一級緩存中獲取ID對應的值,在獲取不到的狀況下,將根據該對象是否配置了二級緩存來作相應的處理。
如配置了二級緩存,則從二級緩存中獲取ID對應的值,如仍然獲取不到則還須要根據是否配置了延遲加載來決定如何執行,如未配置延遲加載則從數據庫中直接獲取。在從數據庫獲取到數據的狀況下,Hibernate會相應地填充一級緩存和二級緩存,如配置了延遲加載則直接返回一個代理類,只有在觸發代理類的調用時才進行數據庫的查詢操做。
在Session一直打開的狀況下,並在該對象具備單向關聯維護的時候,須要使用相似Session.clear(),Session.evict()的方法來強制刷新一級緩存。
● get()方法
get()方法與load()方法的區別就在於不會查找二級緩存。在當前Session的一級緩存中獲取不到指定的對象時,會直接執行查詢語句從數據庫中得到所須要的數據。
在Hibernate中,能夠經過HQL來執行對數據庫的查詢操做。具體的查詢是由Query對象的list()和iterator()方法來執行的。這兩個方法在執行查詢時的處理方法存在着必定的差異,在開發中應該依據具體的狀況來選擇合適的方法。
● list()方法
在執行Query的list()方法時,Hibernate的作法是首先檢查是否配置了查詢緩存,如配置了則從查詢緩存中尋找是否已經對該查詢進行了緩存,如獲取不到則從數據庫中進行獲取。從數據庫中獲取到後,Hibernate將會相應地填充一級、二級和查詢緩存。如獲取到的爲直接的結果集,則直接返回,如獲取到的爲一些ID的值,則再根據ID獲取相應的值(Session.load()),最後造成結果集返回。能夠看到,在這樣的狀況下,list()方法也是有可能形成N次查詢的。
查詢緩存在數據發生任何變化的狀況下都會被自動清空。
● iterator()方法
Query的iterator()方法處理查詢的方式與list()方法是不一樣的,它首先會使用查詢語句獲得ID值的列表,而後再使用Session的load()方法獲得所須要的對象的值。
在獲取數據的時候,應該依據這4種獲取數據方式的特色來選擇合適的方法。在開發中能夠經過設置show_sql選項來輸出Hibernate所執行的SQL語句,以此來了解Hibernate是如何操做數據庫的。
posted @ 2009-07-19 21:29 jadmin 閱讀(2) 評論(0) 編輯
查詢緩存是專門針對各類查詢操做進行緩存。查詢緩存會在整個SessionFactory的生命週期中起做用,存儲的方式也是採用key-value的形式來進行存儲的。
查詢緩存中的key是根據查詢的語句、查詢的條件、查詢的參數和查詢的頁數等信息組成的。而數據的存儲則會使用兩種方式,使用SELECT語句只查詢實體對象的某些列或者某些實體對象列的組合時,會直接緩存整個結果集。而對於查詢結果爲某個實體對象集合的狀況則只會緩存實體對象的ID值,以達到緩存空間能夠共用,節省空間的目的。
在使用查詢緩存時,除了須要設置hibernate.cache.provider_class參數來啓動二級緩存外,還須要經過hibernate.cache.use_query_cache參數來啓動對查詢緩存的支持。
另外須要注意的是,查詢緩存是在執行查詢語句的時候指定緩存的方式以及是否須要對查詢的結果進行緩存。
下面就來了解一下查詢緩存的使用方法及做用。
修改Hibernate配置文件
首先須要修改Hibernate的配置文件,增長hibernate.cache.use_query_cache參數的配置。配置方法以下:
<property name="hibernate.cache.use_query_cache">true</property>
Hibernate配置文件的詳細內容請參考配套光盤中的hibernate\src\cn\hxex\ hibernate\cache\hibernate.cfg.xml文件。
編寫主測試程序
因爲這是在前面二級緩存例子的基礎上來開發的,因此,對於EHCache的配置以及視圖對象的開發和映射文件的配置工做就都不須要再從新進行了。下面就來看一下主測試程序的實現方法,如清單14.11所示。
清單14.11 主程序的實現
……
public void run() {
SessionFactory sf = QueryCacheMain.getSessionFactory();
Session session = sf.getCurrentSession();
session.beginTransaction();
Query query = session.createQuery( "from User" );
Iterator it = query.setCacheable( true ).list().iterator();
while( it.hasNext() ) {
System.out.println( it.next() );
}
User user = (User)session.get( User.class, "1" );
System.out.println( user );
session.getTransaction().commit();
}
public static void main(String[] args) {
QueryCacheMain main1 = new QueryCacheMain();
main1.start();
try {
Thread.sleep( 2000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
QueryCacheMain main2 = new QueryCacheMain();
main2.start();
}
}
主程序在實現的時候採用了多線程的方式來運行。首先將「from User」查詢結果進行緩存,而後再經過ID取得對象來檢查是否對對象進行了緩存。另外,多個線程的執行能夠看出對於進行了緩存的查詢是不會執行第二次的。
運行測試主程序
接着就來運行測試主程序,其輸出結果應該以下所示:
Hibernate: select user0_.userId as userId0_, user0_.name as name0_, user0_.age as age0_ from USERINFO user0_
ID: 1
Namge: galaxy
Age: 32
ID: 1
Namge: galaxy
Age: 32
ID: 1
Namge: galaxy
Age: 32
ID: 1
Namge: galaxy
Age: 32
經過上面的執行結果能夠看到,在兩個線程執行中,只執行了一個SQL查詢語句。這是由於根據ID所要獲取的對象在前面的查詢中已經獲得了,並進行了緩存,因此沒有再次執行查詢語句。
posted @ 2009-07-19 21:25 jadmin 閱讀(1) 評論(0) 編輯
與Session相對的是,SessionFactory也提供了相應的緩存機制。SessionFactory緩存能夠依據功能和目的的不一樣而劃分爲內置緩存和外置緩存。
SessionFactory的內置緩存中存放了映射元數據和預約義SQL語句,映射元數據是映射文件中數據的副本,而預約義SQL語句是在Hibernate初始化階段根據映射元數據推導出來的。SessionFactory的內置緩存是隻讀的,應用程序不能修改緩存中的映射元數據和預約義SQL語句,所以SessionFactory不須要進行內置緩存與映射文件的同步。
SessionFactory的外置緩存是一個可配置的插件。在默認狀況下,SessionFactory不會啓用這個插件。外置緩存的數據是數據庫數據的副本,外置緩存的介質能夠是內存或者硬盤。SessionFactory的外置緩存也被稱爲Hibernate的二級緩存。
Hibernate的二級緩存的實現原理與一級緩存是同樣的,也是經過以ID爲key的Map來實現對對象的緩存。
因爲Hibernate的二級緩存是做用在SessionFactory範圍內的,於是它比一級緩存的範圍更廣,能夠被全部的Session對象所共享。
Hibernate的二級緩存同一級緩存同樣,也是針對對象ID來進行緩存。因此說,二級緩存的做用範圍是針對根據ID得到對象的查詢。
二級緩存的工做能夠歸納爲如下幾個部分:
● 在執行各類條件查詢時,若是所得到的結果集爲實體對象的集合,那麼就會把全部的數據對象根據ID放入到二級緩存中。
● 當Hibernate根據ID訪問數據對象的時候,首先會從Session一級緩存中查找,若是查不到而且配置了二級緩存,那麼會從二級緩存中查找,若是還查不到,就會查詢數據庫,把結果按照ID放入到緩存中。
● 刪除、更新、增長數據的時候,同時更新緩存。
Hibernate的二級緩存做爲一個可插入的組件在使用的時候也是能夠進行配置的,但並非全部的對象都適合放在二級緩存中。
在一般狀況下會將具備如下特徵的數據放入到二級緩存中:
● 不多被修改的數據。
● 不是很重要的數據,容許出現偶爾併發的數據。
● 不會被併發訪問的數據。
● 參考數據。
而對於具備如下特徵的數據則不適合放在二級緩存中:
● 常常被修改的數據。
● 財務數據,絕對不容許出現併發。
● 與其餘應用共享的數據。
在這裏特別要注意的是對放入緩存中的數據不能有第三方的應用對數據進行更改(其中也包括在本身程序中使用其餘方式進行數據的修改,例如,JDBC),由於那樣Hibernate將不會知道數據已經被修改,也就沒法保證緩存中的數據與數據庫中數據的一致性。
在默認狀況下,Hibernate會使用EHCache做爲二級緩存組件。可是,能夠經過設置hibernate.cache.provider_class屬性,指定其餘的緩存策略,該緩存策略必須實現org.hibernate.cache.CacheProvider接口。
經過實現org.hibernate.cache.CacheProvider接口能夠提供對不一樣二級緩存組件的支持。
Hibernate內置支持的二級緩存組件如表14.1所示。
表14.1 Hibernate所支持的二級緩存組件
posted @ 2009-07-19 21:23 jadmin 閱讀(0) 評論(0) 編輯
你們都知道,Hibernate是以JDBC爲基礎實現的持久層組件,於是其性能確定會低於直接使用JDBC來訪問數據庫。所以,爲了提升Hibernate的性能,在Hibernate組件中提供了完善的緩存機制來提升數據庫訪問的性能。
什麼是緩存
緩存是介於應用程序和物理數據之間的,其做用是爲了下降應用程序對物理數據訪問的頻次從而提升應用系統的性能。緩存思想的提出主要是由於對物理數據的訪問效率要遠遠低於對內存的訪問速度,於是採用了將部分物理數據存放於內存當中,這樣能夠有效地減小對物理數據的訪問次數,從而提升系統的性能。
緩存普遍地存在於咱們所接觸的各類應用系統中,例如數據庫系統、Windows操做系統等,在進行物理數據的訪問時無一例外地都使用了緩存機制來提升操做的性能。
緩存內的數據是對物理數據的複製,所以一個緩存系統所應該包括的最基本的功能是數據的緩存和讀取,同時在使用緩存的時候還要考慮緩存中的數據與物理數據的同步,也就是要保持二者是一致的。
緩存要求對數據的讀寫速度很高,所以,通常狀況下會選用內存做爲存儲的介質。但若是內存有限,而且緩存中存放的數據量很是大時,也會用硬盤做爲緩存介質。緩存的實現不只僅要考慮存儲的介質,還要考慮到管理緩存的併發訪問和緩存數據的生命週期。
爲了提升系統的性能,Hibernate也使用了緩存的機制。在Hibernate框架中,主要包括如下兩個方面的緩存:一級緩存和二級緩存(包含查詢緩存)。Hibernate中緩存的做用主要表如今如下兩個方面:
● 經過主鍵(ID)加載數據的時候
● 延遲加載中
一級緩存
Hibernate的一級緩存是由Session提供的,所以它只存在於Session的生命週期中,也就是當Session關閉的時候該Session所管理的一級緩存也會當即被清除。
Hibernate的一級緩存是Session所內置的,不能被卸載,也不能進行任何配置。
一級緩存採用的是key-value的Map方式來實現的,在緩存實體對象時,對象的主關鍵字ID是Map的key,實體對象就是對應的值。因此說,一級緩存是以實體對象爲單位進行存儲的,在訪問的時候使用的是主關鍵字ID。
雖然,Hibernate對一級緩存使用的是自動維護的功能,沒有提供任何配置功能,可是能夠經過Session中所提供的方法來對一級緩存的管理進行手工干預。Session中所提供的干預方法包括如下兩種。
● evict() :用於將某個對象從Session的一級緩存中清除。
● clear() :用於將一級緩存中的對象所有清除。
在進行大批量數據一次性更新的時候,會佔用很是多的內存來緩存被更新的對象。這時就應該階段性地調用clear()方法來清空一級緩存中的對象,控制一級緩存的大小,以免產生內存溢出的狀況。具體的實現方法如清單14.8所示。
清單14.8 大批量更新時緩存的處理方法
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
for ( int i=0; i<100000; i++ ) {
Customer customer = new Customer(……);
session.save(customer);
if ( i % 20 == 0 ) {
//將本批插入的對象當即寫入數據庫並釋放內存
session.flush();
session.clear();
}
}
tx.commit();
session.close();
posted @ 2009-07-19 21:18 jadmin 閱讀(0) 評論(0) 編輯
併發控制
當數據庫系統採用Read Committed隔離級別時,會致使不可重複讀取和兩次更新丟失的併發問題,能夠在應用程序中採用鎖機制來避免這類問題的產生。
從應用程序的角度上看,鎖能夠分爲樂觀鎖和悲觀鎖兩大類。
悲觀鎖
在多個客戶端可能讀取同一筆數據或同時更新一筆數據的狀況下,必需要有訪問控制的手段,防止同一個數據被修改而形成混亂,最簡單的手段就是對數據進行鎖定。在本身進行數據讀取或更新等動做時,鎖定其餘客戶端不能對同一筆數據進行任何的動做。
悲觀鎖(Pessimistic Locking),如其名稱所示,悲觀地認定每次資料存取時,其餘的客戶端也會存取同一筆數據,所以將會鎖住該筆數據,直到本身操做完成後再解除鎖。
悲觀鎖假定任什麼時候刻存取數據時,均可能有另外一個客戶也正在存取同一筆數據,於是對數據採起了數據庫層次的鎖定狀態,在鎖定的時間內其餘的客戶不能對數據進行存取。對於單機或小系統而言,這並不成問題,然而若是是在網絡上的系統,同時間會有許多訪問的機器,若是每一次讀取數據都形成鎖定,其後繼的存取就必須等待,這將形成效能上的問題,形成後繼使用者的長時間等待。
悲觀鎖一般透過系統或數據庫自己的功能來實現,依賴系統或數據庫自己提供的鎖機制。Hibernate便是如此,能夠利用Query或Criteria的setLockMode()方法來設定要鎖定的表或列及其鎖模式,可設定的鎖模式有如下幾個。
LockMode.UPGRADE:利用數據庫的for update子句進行鎖定。
LockMode.UPGRADE_NOWAIT:使用for update nowait子句進行鎖定,在Oracle數據庫中使用。
下面來實現一個簡單的例子,測試一下采用悲觀鎖時數據庫是如何進行操做的。
首先來完成一個實體對象——User,該對象包含了id,name和age三個屬性,實現的方法如清單14.1所示。
清單14.1 User對象的實現
package cn.hxex.hibernate.lock;
public class User {
private String id;
private String name;
private Integer age;
// 省略了getter和setter方法
……
}
接下來就是映射文件的配置,因爲該映射文件沒有涉及到任何與其餘對象的關聯配置,因此實現的方法也很是簡單,代碼如清單14.2所示。
清單14.2 User映射文件的實現
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="cn.hxex.hibernate.lock">
<class name="User" table="USERINFO">
<id name="id" column="userId">
<generator class="uuid.hex"/>
</id>
<property name="name" column="name" type="java.lang.String"/>
<property name="age" column="age" type="java.lang.Integer"/>
</class>
</hibernate-mapping>
另一件重要的工做就是Hibernate的配置文件了,在這個配置文件中包含了鏈接數據庫的參數以及其餘一些重要的參數,實現的方法如清單14.3所示。
清單14.3 Hibernate配置文件的實現
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- 數據庫的URL -->
<!-- property name="hibernate.connection.url">
jdbc:oracle:thin:@192.168.10.121:1521:HiFinance</property-->
<property name="hibernate.connection.url">
jdbc:mysql://localhost:3306/lockdb?useUnicode=true&characterEncoding=utf8&autoReconnect=true&autoReconnectForPools=true
</property>
<!-- 數據庫的驅動程序 -->
<!-- property name="hibernate.connection.driver_class">
oracle.jdbc.driver.OracleDriver</property-->
<property name="hibernate.connection.driver_class">
com.mysql.jdbc.Driver
</property>
<!-- 數據庫的用戶名 -->
<property name="hibernate.connection.username">lockdb</property>
<!-- 數據庫的密碼 -->
<property name="hibernate.connection.password">lockdb</property>
<!-- 數據庫的Dialect -->
<!-- property name="hibernate.dialect">
org.hibernate.dialect.Oracle9Dialect</property -->
<property name="hibernate.dialect">
org.hibernate.dialect.MySQLDialect</property>
<!-- 輸出執行的SQL語句 -->
<property name="hibernate.show_sql">true</property>
<property name="hibernate.current_session_context_class">thread</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- HBM文件列表 -->
<mapping resource="cn/hxex/hibernate/lock/User.hbm.xml" />
</session-factory>
</hibernate-configuration>
最後要實現的就是測試主程序了,在測試主程序中包含了Hibernate的初始化代碼以及悲觀鎖的測試方法。測試主程序的實現方法如清單14.4所示。
清單14.4 測試主程序的實現
package cn.hxex.hibernate.lock;
import java.net.URL;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.LockMode;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class LockMain {
private static Log log = LogFactory.getLog( LockMain.class );
// 靜態Configuration和SessionFactory對象的實例(全局惟一的)
private static Configuration configuration;
private static SessionFactory sessionFactory;
static
{
// 從默認的配置文件建立SessionFactory
try
{
URL configURL = ClassLoader.getSystemResource(
"cn/hxex/hibernate/lock/hibernate.cfg.xml" );
// 建立默認的Configuration對象的實例
configuration = new Configuration();
// 讀取hibernate.properties或者hibernate.cfg.xml文件
configuration.configure( configURL );
// 使用靜態變量來保持SessioFactory對象的實例
sessionFactory = configuration.buildSessionFactory();
}
catch (Throwable ex)
{
// 輸出異常信息
log.error("Building SessionFactory failed.", ex);
ex.printStackTrace();
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
public void testPessimisticLock() {
SessionFactory sf = LockMain.getSessionFactory();
Session session = sf.getCurrentSession();
session.beginTransaction();
Query query = session.createQuery("from User user");
query.setLockMode("user", LockMode.UPGRADE);
List users = query.list();
for( int i=0; i<users.size(); i++ ) {
System.out.println( users.get( i ) );
}
session.getTransaction().commit();
}
public static void main(String[] args) {
LockMain main = new LockMain();
main.testPessimisticLock();
}
}
在上面的清單中,testPessimisticLock()方法就是測試悲觀鎖的方法,該方法在執行查詢以前經過Query對象的setLockMode()方法設置了訪問User對象的模式,這樣,這個程序在執行的時候就會使用如下的SQL語句:
select user0_.userId as userId0_, user0_.name as name0_, user0_.age as age0_
from USERINFO user0_ for update
除了Query對象外,也能夠在使用Session的load()或是lock()時指定鎖模式。
除了前面所說起的兩種鎖模式外,還有三種Hibernate內部自動對數據進行加鎖的模式,但它的處理是與數據庫無關的。
LockMode.WRITE:在insert或update時進行鎖定,Hibernate會在調用save()方法時自動得到鎖。
LockMode.READ:在讀取記錄時Hibernate會自動得到鎖。
LockMode.NONE:沒有鎖。
若是數據庫不支持所指定的鎖模式,Hibernate會選擇一個合適的鎖替換,而不是拋出一個異常。
樂觀鎖
樂觀鎖(Optimistic Locking)認爲資料的存取不多發生同時存取的問題,於是不作數據庫層次上的鎖定。爲了維護正確的數據,樂觀鎖是使用應用程序上的邏輯來實現版本控制的。
在使用樂觀鎖策略的狀況下,數據不一致的狀況一旦發生,有幾個解決方法,一種是先更新爲主,一種是後更新爲主,比較複雜的就是檢查發生變更的數據來實現,或是檢查全部屬性來實現樂觀鎖。
Hibernate中經過檢查版本號來判斷數據是否已經被其餘人所改動,這也是Hibernate所推薦的方式。在數據庫中加入一個version字段記錄,在讀取數據時連同版本號一同讀取,並在更新數據時比較版本號與數據庫中的版本號,若是等於數據庫中的版本號則予以更新,並遞增版本號,若是小於數據庫中的版本號就拋出異常。
下面就來在前面例子的基礎上進行Hibernate樂觀鎖的測試。
首先須要修改前面所實現的業務對象,在其中增長一個version屬性,用來記錄該對象所包含數據的版本信息,修改後的User對象如清單14.5所示。
清單14.5 修改後的User對象
package cn.hxex.hibernate.lock;
public class User {
private String id;
private Integer version; // 增長版本屬性
private String name;
private Integer age;
// 省略了getter和setter方法
……
}
而後是修改映射文件,增長version屬性的配置。在這裏須要注意的是,這裏的version屬性應該使用專門的<version>元素來進行配置,這樣才能使其發揮樂觀鎖的做用。若是還使用<property>元素來進行配置,那麼Hibernate只會將其做爲一個普通的屬性來進行處理。
修改後的映射文件如清單14.6所示。
清單14.6 修改後的映射文件
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="cn.hxex.hibernate.lock">
<class name="User" table="USERINFO" optimistic-lock="version">
<id name="id" column="userId">
<generator class="uuid.hex"/>
</id>
<version name="version" column="version" type="java.lang.Integer"/>
<property name="name" column="name" type="java.lang.String"/>
<property name="age" column="age" type="java.lang.Integer"/>
</class>
</hibernate-mapping>
接下來還要進行測試主程序的修改。因爲須要模擬兩我的同時修改同一個記錄的狀況,因此在這裏須要將主程序修改成是能夠多線程執行的,而後在run()方法中,調用對User對象的修改程序。
實現後的主測試程序如清單14.7所示。
清單14.7 修改後的測試主程序
package cn.hxex.hibernate.lock;
import java.net.URL;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.LockMode;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
public class LockMain extends Thread{
……
public void testOptimisticLock() {
SessionFactory sf = LockMain.getSessionFactory();
Session session = sf.openSession();
Transaction tx = session.beginTransaction();
User userV1 = (User)session.load( User.class, "1" );
// 等第二個進程執行
try {
sleep( 3000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
userV1.setAge(new Integer(32));
tx.commit();
session.close();
}
public void run() {
testOptimisticLock();
}
public static void main(String[] args) {
// LockMain main = new LockMain();
// main.testPessimisticLock();
LockMain main1 = new LockMain();
main1.start();
LockMain main2 = new LockMain();
main2.start();
}
}
最後,執行測試主程序,在控制檯中應該看到相似下面的輸出:
Hibernate: select user0_.userId as userId0_0_, user0_.version as version0_0_, user0_.name as name0_0_, user0_.age as age0_0_ from USERINFO user0_ where user0_.userId=?
Hibernate: select user0_.userId as userId0_0_, user0_.version as version0_0_, user0_.name as name0_0_, user0_.age as age0_0_ from USERINFO user0_ where user0_.userId=?
Hibernate: update USERINFO set version=?, name=?, age=? where userId=? and version=?
Hibernate: update USERINFO set version=?, name=?, age=? where userId=? and version=?
2006-10-3 21:27:20 org.hibernate.event.def.AbstractFlushingEventListener performExecutions
嚴重: Could not synchronize database state with session
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [cn.hxex.hibernate.lock.User#1]
……
在Hibernate所執行的UPDATE語句中能夠看到,version字段是做爲更新的條件來執行的。對於第二個進程來講,因爲數據庫中的記錄已經被第一個進程更新(更新的同時會致使version自動增長),就必然會致使第二個進程操做的失敗。Hibernate正是利用這種機制來避免兩次更新問題的出現。
posted @ 2009-07-19 21:11 jadmin 閱讀(6) 評論(0) 編輯
在如今的B/S體系結構的軟件開發中,對於數據庫事務處理中最常使用的方式是每一個用戶請求一個事務。也就是說,當服務器端接收到一個用戶請求後,會開始一個新的事務,直到對用戶請求的全部處理都進行完畢而且完成了響應用戶請求的全部輸出以後纔會關閉這個事務。
對於使用Hibernate實現持久化功能的系統來講,事務的處理是這樣的:服務器端在接收到用戶的請求後,會建立一個新的Hibernate Session對象,而後經過該Session對象開始一個新的事務而且以後全部對數據庫的操做都經過該Session對象來進行。最後,完成將響應頁面發送到客戶端的工做後再提交事務而且關閉Session。
Session的對象是輕型的,非線程安全的,因此在每次用戶請求時建立,請求處理完畢後丟棄。
那麼,該如何實現這種方式的事務處理呢?處理的難點在於如何在業務處理以前建立Session並開始事務以及在業務處理以後提交事務並關閉Session。對於如今的Web應用來講,一般狀況下是經過ServletFilter來完成事務處理的操做。這樣,就能夠輕鬆地實如今用戶請求到達服務器端的時候建立Session並開始事務,而服務器端響應處理結束以前提交事務並關閉Session。
另一個問題是,在ServletFilter中建立的Session是如何傳遞給業務處理方法中的呢?處理的方法是經過一個ThreadLocal變量來把建立的Session對象綁定處處理用戶請求的線程上去,這樣就能夠使任何的業務處理方法能夠輕鬆獲得Session對象。
Hibernate中事務處理的具體方法能夠參照前面的網絡博客的實例。
可是這種事務處理的方式仍是會遇到一些問題,其中最突出的就是更新衝突的問題。例如,某個操做人員進入了用戶信息的修改頁面,在通過一段時間的對用戶信息的修改後,進行提交操做,而與此同時可能會有另一個操做人員也進行了相同的操做,這樣在處理提交的時候就會產生衝突。
產生這個衝突的緣由在於在開發中須要使用多個數據庫事務來實現一個應用事務。也就是說,在應用程序層,應該將讀取用戶信息、顯示修改頁面以及用戶提交工做來做爲一個事務進行處理,在處理的過程當中應該避免其餘操做人員進行相似的操做。
回想前面的介紹,咱們對於數據庫事務所採起的策略是每一個用戶請求一個事務,而上面的業務處理則至少須要兩個請求才能完成。這樣,二者之間就存在着必定的矛盾,這也就致使了不可重複讀取和兩次更新問題的發生。
爲了解決併發中數據訪問的問題,一般會採用鎖的機制來實現數據訪問的排他性,從而避免兩次更新問題的發生。
posted @ 2009-07-19 21:07 jadmin 閱讀(0) 評論(0) 編輯
爲了不上面出現的幾種狀況,在標準SQL規範中,定義了4個事務隔離級別,不一樣的隔離級別對事務的處理不一樣。
● 未受權讀取(Read Uncommitted):容許髒讀取,但不容許更新丟失。若是一個事務已經開始寫數據,則另一個數據則不容許同時進行寫操做,但容許其餘事務讀此行數據。該隔離級別能夠經過「排他寫鎖」實現。
● 受權讀取(Read Committed):容許不可重複讀取,但不容許髒讀取。這能夠經過「瞬間共享讀鎖」和「排他寫鎖」實現。讀取數據的事務容許其餘事務繼續訪問該行數據,可是未提交的寫事務將會禁止其餘事務訪問該行。
● 可重複讀取(Repeatable Read):禁止不可重複讀取和髒讀取,可是有時可能出現幻影數據。這能夠經過「共享讀鎖」和「排他寫鎖」實現。讀取數據的事務將會禁止寫事務(但容許讀事務),寫事務則禁止任何其餘事務。
● 序列化(Serializable):提供嚴格的事務隔離。它要求事務序列化執行,事務只能一個接着一個地執行,但不能併發執行。若是僅僅經過「行級鎖」是沒法實現事務序列化的,必須經過其餘機制保證新插入的數據不會被剛執行查詢操做的事務訪問到。
隔離級別越高,越能保證數據的完整性和一致性,可是對併發性能的影響也越大。對於多數應用程序,能夠優先考慮把數據庫系統的隔離級別設爲Read Committed,它可以避免髒讀取,並且具備較好的併發性能。儘管它會致使不可重複讀、虛讀和第二類丟失更新這些併發問題,在可能出現這類問題的個別場合,能夠由應用程序採用悲觀鎖或樂觀鎖來控制。
經過前面的介紹已經知道,經過選用不一樣的隔離等級就能夠在不一樣程度上避免前面所說起的在事務處理中所面臨的各類問題。因此,數據庫隔離級別的選取就顯得尤其重要,在選取數據庫的隔離級別時,應該注意如下幾個處理的原則:
首先,必須排除「未受權讀取」,由於在多個事務之間使用它將會是很是危險的。事務的回滾操做或失敗將會影響到其餘併發事務。第一個事務的回滾將會徹底將其餘事務的操做清除,甚至使數據庫處在一個不一致的狀態。極可能一個已回滾爲結束的事務對數據的修改最後卻修改提交了,由於「未受權讀取」容許其餘事務讀取數據,最後整個錯誤狀態在其餘事務之間傳播開來。
其次,絕大部分應用都無須使用「序列化」隔離(通常來講,讀取幻影數據並非一個問題),此隔離級別也難以測量。目前使用序列化隔離的應用中,通常都使用悲觀鎖,這樣強行使全部事務都序列化執行。
剩下的也就是在「受權讀取」和「可重複讀取」之間選擇了。咱們先考慮可重複讀取。若是全部的數據訪問都是在統一的原子數據庫事務中,此隔離級別將消除一個事務在另一個併發事務過程當中覆蓋數據的可能性(第二個事務更新丟失問題)。這是一個很是重要的問題,可是使用可重複讀取並非解決問題的惟一途徑。
假設使用了「版本數據」,Hibernate會自動使用版本數據。Hibernate的一級Session緩存和版本數據已經爲你提供了「可重複讀取隔離」絕大部分的特性。特別是,版本數據能夠防止二次更新丟失的問題,一級Session緩存能夠保證持久載入數據的狀態與其餘事務對數據的修改隔離開來,所以若是使用對全部的數據庫事務採用受權讀取隔離和版本數據是行得通的。
「可重複讀取」爲數據庫查詢提供了更好的效率(僅對那些長時間的數據庫事務),可是因爲幻影讀取依然存在,所以不必使用它(對於Web應用來講,通常也不多在一個數據庫事務中對同一個表查詢兩次)。
也能夠同時考慮選擇使用Hibernate的二級緩存,它能夠如同底層的數據庫事務同樣提供相同的事務隔離,可是它可能弱化隔離。假如在二級緩存大量使用緩存併發策略,它並不提供重複讀取語義(例如,後面章節中將要討論的讀寫,特別是非嚴格讀寫),很容易能夠選擇默認的隔離級別:由於不管如何都沒法實現「可重複讀取」,所以就更沒有必要拖慢數據庫了。另外一方面,可能對關鍵類不採用二級緩存,或者採用一個徹底的事務緩存,提供「可重複讀取隔離」。那麼在業務中須要使用到「可重複讀取」嗎?若是你喜歡,固然能夠那樣作,但更多的時候並無必要花費這個代價。
posted @ 2009-07-19 21:04 jadmin 閱讀(0) 評論(0) 編輯
數據庫的事務處理是在進行數據庫應用開發中必須進行處理的一個問題。那麼對於選擇Hibernate做爲持久層組件,瞭解Hibernate的事務處理機制就顯得尤其重要了。
事務的基本概念
事務(Transaction)是併發控制的基本單位。所謂的事務,它是一個操做序列,這些操做要麼都執行,要麼都不執行,它是一個不可分割的工做單位。例如,銀行轉帳工做:從一個帳號扣款並使另外一個帳號增款,這兩個操做要麼都執行,要麼都不執行。因此,應該把它們當作一個事務。事務是數據庫維護數據一致性的單位,在每一個事務結束時,都能保持數據一致性。
針對上面的描述能夠看出,事務的提出主要是爲了解決併發狀況下保持數據一致性的問題。
事務具備如下4個基本特徵。
● Atomic(原子性):事務中包含的操做被看作一個邏輯單元,這個邏輯單元中的操做要麼所有成功,要麼所有失敗。
● Consistency(一致性):只有合法的數據能夠被寫入數據庫,不然事務應該將其回滾到最初狀態。
● Isolation(隔離性):事務容許多個用戶對同一個數據進行併發訪問,而不破壞數據的正確性和完整性。同時,並行事務的修改必須與其餘並行事務的修改相互獨立。
● Durability(持久性):事務結束後,事務處理的結果必須可以獲得固化。
數據庫確定是要被廣大客戶所共享訪問的,那麼在數據庫操做過程當中極可能出現如下幾種不肯定狀況。
● 更新丟失(Lost update):兩個事務都同時更新一行數據,可是第二個事務卻中途失敗退出,致使對數據的兩個修改都失效了。這是由於系統沒有執行任何的鎖操做,所以併發事務並無被隔離開來。
● 髒讀取(Dirty Reads):一個事務開始讀取了某行數據,可是另一個事務已經更新了此數據但沒有可以及時提交。這是至關危險的,由於極可能全部的操做都被回滾。
● 不可重複讀取(Non-repeatable Reads):一個事務對同一行數據重複讀取兩次,可是卻獲得了不一樣的結果。例如,在兩次讀取的中途,有另一個事務對該行數據進行了修改,並提交。
● 兩次更新問題(Second lost updates problem):沒法重複讀取的特例。有兩個併發事務同時讀取同一行數據,而後其中一個對它進行修改提交,而另外一個也進行了修改提交。這就會形成第一次寫操做失效。
● 虛讀(Phantom Reads):事務在操做過程當中進行兩次查詢,第二次查詢的結果包含了第一次查詢中未出現的數據(這裏並不要求兩次查詢的SQL語句相同)。這是由於在兩次查詢過程當中有另一個事務插入數據形成的。
posted @ 2009-07-19 21:04 jadmin 閱讀(2) 評論(0) 編輯
HibernateTemplate還提供了一種更加靈活的方式來操做數據庫,經過這種方式能夠徹底使用Hibernate的操做方式。HibernateTemplate的靈活訪問方式可經過以下兩個方法完成:
● Object execute(HibernateCallback action)。
● List execute(HibernateCallback action)。
這兩個方法都須要一個HibernateCallback的實例,HibernateCallback實例可在任何有效的Hibernate數據訪問中使用。程序開發者經過HibernateCallback,能夠徹底使用Hibernate靈活的方式來訪問數據庫,解決Spring封裝Hibernate後靈活性不足的缺陷。
HibernateCallback是一個接口,該接口包含一個方法doInHibernate(org.hibernate. Session session),該方法只有一個參數Session。在開發中提供HibernateCallback實現類時,必須實現接口裏包含的doInHibernate方法,在該方法體內便可得到Hibernate Session的引用,一旦得到了Hibernate Session的引用,就能夠徹底以Hibernate的方式進行數據庫訪問。
注意:doInHibernate方法內能夠訪問Session,該Session對象是綁定在該線程的Session實例。該方法內的持久層操做,與不使用Spring時的持久層操做徹底相同。這保證了對於複雜的持久層訪問,依然能夠使用Hibernate的訪問方式。
下面的代碼對HibernateDaoSupport類進行擴展(雖然Spring 2.0的HibernateTemplate提供了一個分頁方法setMaxResults,但僅此一個方法依然不能實現分頁查詢),這種擴展主要是爲該類增長了3個分頁查詢的方法,分頁查詢時必須直接調用Hibernate的Session完成,所以,必須藉助於HibernateCallBack的幫助。
public class YeekuHibernateDaoSupport extends HibernateDaoSupport
{
/**
* 使用hql 語句進行分頁查詢操做
* @param hql 須要查詢的hql語句
* @param offset 第一條記錄索引
* @param pageSize 每頁須要顯示的記錄數
* @return 當前頁的全部記錄
*/
public List findByPage(final String hql,
final int offset, final int pageSize)
{
//HibernateDaoSupport已經包含了getHibernateTemplate()方法
List list = getHibernateTemplate().executeFind(new
HibernateCallback()
{
public Object doInHibernate(Session session)
throws HibernateException, SQLException
//該方法體內以Hibernate方法進行持久層訪問
{
List result = session.createQuery(hql)
.setFirstResult(offset)
.setMaxResults(pageSize)
.list();
return result;
}
});
return list;
}
/**
* 使用hql 語句進行分頁查詢操做
* @param hql 須要查詢的hql語句
* @param value 若是hql有一個參數須要傳入,value就是傳入的參數
* @param offset 第一條記錄索引
* @param pageSize 每頁須要顯示的記錄數
* @return 當前頁的全部記錄
*/
public List findByPage(final String hql , final Object value ,
final int offset, final int pageSize)
{
List list = getHibernateTemplate().executeFind(new
HibernateCallback()
{
public Object doInHibernate(Session session)
throws HibernateException, SQLException
{
//下面查詢的是最簡單的Hiberante HQL查詢
List result = session.createQuery(hql)
.setParameter(0, value)
.setFirstResult(offset)
.setMaxResults(pageSize)
.list();
return result;
}
});
return list;
}
/**
* 使用hql 語句進行分頁查詢操做
* @param hql 須要查詢的hql語句
* @param values 若是hql有多個參數須要傳入,values就是傳入的參數數組
* @param offset 第一條記錄索引
* @param pageSize 每頁須要顯示的記錄數
* @return 當前頁的全部記錄
*/
public List findByPage(final String hql, final Object[] values,
final int offset, final int pageSize)
{
List list = getHibernateTemplate().executeFind(new
HibernateCallback()
{
public Object doInHibernate(Session session)
throws HibernateException, SQLException
{
Query query = session.createQuery(hql);
for (int i = 0 ; i < values.length ; i++)
{
query.setParameter( i, values[i]);
}
List result = query.setFirstResult(offset)
.setMaxResults(pageSize)
.list();
return result;
}
});
return list;
}
}
在上面的代碼實現中,直接使用了getHibernateTemplate()方法,這個方法由Hibernate- DaoSupport提供。而YeekuHibernateDaoSupport是HibernateDaoSupport的子類,所以,能夠直接使用該方法。
當實現doInHibernate(Session session)方法時,徹底以Hibernate的方式進行數據庫訪問,這樣保證了Hibernate進行數據庫訪問的靈活性。
注意:Spring提供的XxxTemplate和XxxCallBack互爲補充,兩者體現了Spring框架設計的用心良苦:XxxTemplate對通用操做進行封裝,而XxxCallBack解決了封裝後靈活性不足的缺陷。
爲了實現DAO組件,Spring提供了大量的XxxDaoSupport類,這些DAO支持類對於實現DAO組件大有幫助,由於這些DAO支持類已經完成了大量基礎性工做。
Spring爲Hibernate的DAO提供了工具類HibernateDaoSupport。該類主要提供以下兩個方法以方便DAO的實現:
● public final HibernateTemplate getHibernateTemplate()。
● public final void setSessionFactory(SessionFactory sessionFactory)。
其中,setSessionFactory方法可用於接收Spring的ApplicationContext的依賴注入,可接收配置在Spring的SessionFactory實例,getHibernateTemplate方法用於返回經過SessionFactory產生的HibernateTemplate實例,持久層訪問依然經過HibernateTemplate實例完成。
下面實現的DAO組件繼承了Spring提供的HibernateDaoSupport類,依然實現了PersonDao接口,其功能與前面提供的PersonDao實現類徹底相同。其代碼以下:
public class PersonDaoHibernate extends HibernateDaoSupport implements PersonDao
{
/**
* 加載人實例
* @param id 須要加載的Person實例的主鍵值
* @return 返回加載的Person實例
*/
public Person get(int id)
{
return (Person)getHibernateTemplate().get(Person.class, new
Integer(id));
}
/**
* 保存人實例
* @param person 須要保存的Person實例
*/
public void save(Person person)
{
getHibernateTemplate().save(person);
}
/**
* 修改Person實例
* @param person 須要修改的Person實例
*/
public void update(Person person)
{
getHibernateTemplate().update(person);
}
/**
* 刪除Person實例
* @param id 須要刪除的Person的id
*/
public void delete(int id)
{
getHibernateTemplate().delete(getHibernateTemplate().
get(Person.class, new Integer(id)));
}
/**
* 刪除Person實例
* @param person 須要刪除的Person實例
*/
public void delete(Person person)
{
getHibernateTemplate().delete(person);
}
/**
* 根據用戶名查找Person
* @param name 用戶名
* @return 用戶名對應的所有用戶
*/
public List findByPerson(String name)
{
return getHibernateTemplate().find("from Person p where p.name
like ?" , name);
}
/**
* 返回所有的Person實例
* @return 所有的Person實例
*/
public List findAllPerson()
{
return getHibernateTemplate().find("from Person ");
}
}
上面的代碼與前面的PersonDAOImpl對比會發現,代碼量大大減小。事實上,DAO的實現依然藉助於HibernateTemplate的模板訪問方式,只是HibernateDaoSupport將依賴注入SessionFactory的工做已經完成,獲取HibernateTemplate的工做也已完成。該DAO的配置必須依賴於SessionFactory,配置文件與前面部署DAO組件的方式徹底相同,此處再也不贅述。
在繼承HibernateDaoSupport的DAO實現裏,Hibernate Session的管理徹底不須要打開代碼,而由Spring來管理。Spring會根據實際的操做,採用「每次事務打開一次session」的策略,自動提升數據庫訪問的性能。
至此爲止,J2EE應用所須要的各類組件都已經出現了,從MVC層的控制器組件,到業務邏輯組件,以及持久層的DAO組件,已經所有成功實現。應用程序代碼並未將這些組件耦合在一塊兒,代碼中都是面向接口編程,所以必須利用Spring的IoC容器將他們組合在一塊兒。
從用戶角度來看,用戶發出HTTP請求,當MVC框架的控制器組件攔截到用戶請求時,將調用系統的業務邏輯組件,而業務邏輯組件則調用系統的DAO組件,而DAO組件則依賴於SessionFactory和DataSource等底層組件實現數據庫訪問。
從系統實現角度來看,IoC容器先建立SessionFactory和DataSource等底層組件,而後將這些底層組件注入給DAO組件,提供一個完整的DAO組件,並將此DAO組件注入給業務邏輯組件,從而提供一個完整的業務邏輯組件,而業務邏輯組件又被注入給控制器組件,控制器組件負責攔截用戶請求,並將處理結果呈現給用戶——這一系列的銜接都由Spring的IoC容器提供實現。
下面給出關於如何在容器中配置J2EE組件的大體模板,其模板代碼以下:
<?xml version="1.0" encoding="GBK"?>
<!-- beans是Spring配置文件的根元素,而且指定了Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 定義數據源Bean,使用C3P0數據源實現 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<!-- 指定鏈接數據庫的驅動 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<!-- 指定鏈接數據庫的URL -->
<property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
<!-- 指定鏈接數據庫的用戶名 -->
<property name="user" value="root"/>
<!-- 指定鏈接數據庫的密碼 -->
<property name="password" value="32147"/>
<!-- 指定鏈接數據庫鏈接池的最大鏈接數 -->
<property name="maxPoolSize" value="40"/>
<!-- 指定鏈接數據庫鏈接池的最小鏈接數 -->
<property name="minPoolSize" value="1"/>
<!-- 指定鏈接數據庫鏈接池的初始化鏈接數 -->
<property name="initialPoolSize" value="1"/>
<!-- 指定鏈接數據庫鏈接池的鏈接最大空閒時間 -->
<property name="maxIdleTime" value="20"/>
</bean>
<!-- 定義Hibernate的SessionFactory Bean -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.
LocalSessionFactoryBean">
<!-- 依賴注入數據源,注入的正是上文中定義的dataSource -->
<property name="dataSource" ref="dataSource"/>
<!-- mappingResources屬性用來列出所有映射文件 -->
<property name="mappingResources">
<list>
<!-- 如下用來列出全部的PO映射文件 -->
<value>lee/Person.hbm.xml</value>
<!-- 此處還可列出更多的PO映射文件 -->
</list>
</property>
<!-- 定義Hibernate的SessionFactory屬性 -->
<property name="hibernateProperties">
<props>
<!-- 指定Hibernate的鏈接方言 -->
<prop key="hibernate.dialect">org.hibernate.dialect.
MySQLDialect</prop>
<!-- 指定啓動應用時,是否根據Hibernate映射文件建立數據表 -->
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 配置Person持久化類的DAO Bean -->
<bean id="personDao" class="lee.PersonDaoImpl">
<!-- 採用依賴注入來傳入SessionFactory的引用 -->
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<!-- 下面能以相同的方式配置更多的持久化Bean -->
...
<bean id="myService" class="lee.MyServiceImp">
<!-- 注入業務邏輯組件所必需的DAO組件 -->
<property name="peronDdao" ref=" personDao "/>
<!-- 此處可採用依賴注入更多的DAO組件 -->
...
</bean>
<!-- 配置控制器Bean,設置起做用域爲Request -->
<bean name="/login" class="lee.LoginAction" scope="request">
<!-- 依賴注入控制器所必需的業務邏輯組件 -->
<property name="myService" ref=" myService "/>
</bean>
</beans>
在上面的配置文件中,同時配置了控制器Bean、業務邏輯組件Bean、DAO組件Bean以及一些基礎資源Bean。各組件的組織被解耦到配置文件中,而不是在代碼層次的低級耦合。
當客戶端的HTTP請求向/login.do發送請求時,將被容器中的lee.LoginAction攔截,LoginAction調用myService Bean,myService Bean則調用personDao等系列DAO組件,整個流程將系統中的各組件有機地組織在一塊兒。
注意:在實際應用中,不多會將DAO組件、業務邏輯組件以及控制組件都配置在同一個文件中。而是在不一樣配置文件中,配置相同一組J2EE應用組件。
在上面的配置文件中,部署了控制器組件、業務邏輯組件、DAO組件,幾乎能夠造成一個完整的J2EE應用。但有一個小小的問題:事務控制。系統沒有任何事務邏輯,沒有事務邏輯的應用是不可想象的。
Spring提供了很是簡潔的聲明式事務控制,只須要在配置文件中增長事務控制片斷,業務邏輯代碼無須任何改變。Spring的聲明式事務邏輯,甚至支持在不一樣事務策略之間切換。
配置Spring聲明式事務時,一般推薦使用BeanNameAutoProxyCreator自動建立事務代理。經過這種自動事務代理的配置策略,增長業務邏輯組件,只須要在BeanNameAutoProxyCreator Bean配置中增長一行便可,從而避免了增量式配置。
在上面的配置模板文件中增長以下配置片斷,系統的myService業務邏輯組件將變成事務代理Bean,從而爲業務邏輯方法增長事務邏輯。
<!-- 配置Hibernate的局部事務管理器 -->
<!-- 使用HibernateTransactionManager類,該類是PlatformTransactionManager
接口,針對採用Hibernate持久化鏈接的特定實現 -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.
HibernateTransactionManager">
<!-- HibernateTransactionManager Bean須要依賴注入一個SessionFactory
bean的引用 -->
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<!-- 配置事務攔截器Bean -->
<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.
TransactionInterceptor">
<!-- 事務攔截器bean須要依賴注入一個事務管理器 -->
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<!-- 下面定義事務傳播屬性 -->
<props>
<prop key="insert*">PROPAGATION_REQUIRED</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<!-- 定義BeanNameAutoProxyCreator的Bean後處理器 -->
<bean class="org.springframework.aop.framework.autoproxy.
BeanNameAutoProxyCreator">
<!-- 指定對知足哪些bean name的bean自動生成業務代理 -->
<property name="beanNames">
<!-- 下面是全部須要自動建立事務代理的Bean -->
<list>
<value>myService</value>
<!-- 下面還可增長鬚要增長事務邏輯的業務邏輯Bean -->
...
</list>
<!-- 此處可增長其餘須要自動建立事務代理的Bean -->
</property>
<!-- 下面定義BeanNameAutoProxyCreator所需的攔截器 -->
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value>
<!-- 此處可增長其餘新的Interceptor -->
</list>
</property>
</bean>
一旦增長了如上的配置片斷,系統中的業務邏輯方法就有了事務邏輯。這種聲明式事務配置方式能夠在不一樣的事務策略之間自由切換。
提示:儘可能使用聲明式事務配置方式,而不要在代碼中完成事務邏輯。
posted @ 2009-07-19 10:24 jadmin 閱讀(1) 評論(0) 編輯
時至今日,可能極少有J2EE應用會直接以JDBC方式進行持久層訪問。畢竟,用面向對象的程序設計語言來訪問關係型數據庫,是一件讓人沮喪的事情。大部分時候,J2EE應用都會以ORM框架來進行持久層訪問,在全部的ORM框架中,Hibernate以其靈巧、輕便的封裝贏得了衆多開發者的青睞。
Spring具備良好的開放性,能與大部分ORM框架良好整合。下面將詳細介紹Spring與Hibernate的整合。
DAO模式是一種標準的J2EE設計模式,DAO模式的核心思想是,全部的數據庫訪 問,都經過DAO組件完成,DAO組件封裝了數據庫的增、刪、改等原子操做。而業務邏輯組件則依賴於DAO組件提供的數據庫原子操做,完成系統業務邏輯的實現。
對於J2EE應用的架構,有很是多的選擇,但無論細節如何變換,J2EE應用都大體可分爲以下3層:
● 表現層。
● 業務邏輯層。
● 數據持久層。
輕量級J2EE架構以Spring IoC容器爲核心,承上啓下。其向上管理來自表現層的Action,向下管理業務邏輯層組件,同時負責管理業務邏輯層所需的DAO對象。各層之間負責傳值的是值對象,也就是JavaBean實例。
圖6.5精確地描繪了輕量級J2EE架構的大體情形。
DAO組件是整個J2EE應用的持久層訪問的重要組件,每一個J2EE應用的底層實現都難以離開DAO組件的支持。Spring對實現DAO組件提供了許多工具類,系統的DAO組件可經過繼承這些工具類完成,從而能夠更加簡便地實現DAO組件。
Spring的DAO支持,容許使用相同的方式、不一樣的數據訪問技術,如JDBC、Hibernate或JDO。Spring的DAO在不一樣的持久層訪問技術上提供抽象,應用的持久層訪問基於Spring的DAO抽象。所以,應用程序能夠在不一樣的持久層技術之間切換。
Spring提供了一系列的抽象類,這些抽象將被做爲應用中DAO實現類的父類。經過繼承這些抽象類,Spring簡化了DAO的開發步驟,能以一致的方式使用數據庫訪問技術。無論底層採用JDBC、JDO或Hibernate,應用中均可採用一致的編程模型。
圖6.5 輕量級J2EE應用架構
應用的DAO類繼承這些抽象類,會大大簡化應用的開發。最大的好處是,繼承這些抽象類的DAO能以一致的方式訪問數據庫,意味着應用程序能夠在不一樣的持久層訪問技術中切換。
除此以外,Spring提供了一致的異常抽象,將原有的Checked異常轉換包裝成Runtime異常,於是,編碼時無須捕獲各類技術中特定的異常。Spring DAO體系中的異常,都繼承DataAccessException,而DataAccessException異常是Runtime的,無須顯式捕捉。經過DataAccessException的子類包裝原始異常信息,從而保證應用程序依然能夠捕捉到原始異常信息。
Spring提供了多種數據庫訪問技術的DAO支持,包括Hibernate、JDO、TopLink、iBatis、OJB等。Spring能夠使用相同的訪問模式、不一樣的數據庫訪問技術。就Hibernate的持久層訪問技術而言,Spring提供了以下3個工具類(或接口)來支持DAO組件的實現:
● HibernateDaoSupport。
● HibernateTemplate。
● HibernateCallBack。
前面介紹Hibernate時已經知道,在經過Hibernate進行持久層訪問時,Hibernate的SessionFactory是一個很是重要的對象,它是單個數據庫映射關係編譯後的內存鏡像。大部分狀況下,一個J2EE應用對應一個數據庫,也即對應一個SessionFactory對象。
在純粹的Hibernate訪問中,應用程序須要手動建立SessionFactory實例,可想而知,這不是一個優秀的策略。在實際開發中,但願以一種聲明式的方式管理SessionFactory實例,直接以配置文件來管理SessionFactory實例,在示範Struts的PlugIn擴展點時,大體示範了這種方式(請參閱2.12.1節的內容)。
Spring的IoC容器則提供了更好的管理方式,它不只能以聲明式的方式配置Session- Factory實例,也可充分利用IoC容器的做用,爲SessionFactory注入數據源引用。
下面是Spring配置文件中配置Hibernate SessionFactory的示範代碼:
<?xml version="1.0" encoding="GBK"?>
<!-- beans是Spring配置文件的根元素,而且指定了Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 定義數據源Bean,使用C3P0數據源實現 -->
<bean id="dataSource" class="com.mchange.v2.c3p0. ComboPooledDataSource"
destroy-method="close">
<!-- 指定鏈接數據庫的驅動 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<!-- 指定鏈接數據庫的URL -->
<property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
<!-- 指定鏈接數據庫的用戶名 -->
<property name="user" value="root"/>
<!-- 指定鏈接數據庫的密碼 -->
<property name="password" value="32147"/>
<!-- 指定鏈接數據庫鏈接池的最大鏈接數 -->
<property name="maxPoolSize" value="40"/>
<!-- 指定鏈接數據庫鏈接池的最小鏈接數 -->
<property name="minPoolSize" value="1"/>
<!-- 指定鏈接數據庫鏈接池的初始化鏈接數 -->
<property name="initialPoolSize" value="1"/>
<!-- 指定鏈接數據庫鏈接池的鏈接最大空閒時間 -->
<property name="maxIdleTime" value="20"/>
</bean>
<!-- 定義Hibernate的SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.
LocalSessionFactoryBean">
<!-- 依賴注入數據源,正是上文定義的dataSource -->
<property name="dataSource" ref="dataSource"/>
<!-- mappingResources屬性用來列出所有映射文件 -->
<property name="mappingResources">
<list>
<!-- 如下用來列出全部的PO映射文件 -->
<value>lee/MyTest.hbm.xml</value>
</list>
</property>
<!-- 定義Hibernate的SessionFactory屬性 -->
<property name="hibernateProperties">
<props>
<!-- 指定Hibernate的鏈接方言 -->
<prop key="hibernate.dialect">org.hibernate.dialect.
MySQLDialect</prop>
<!-- 配置啓動應用時,是否根據Hibernate映射自動建立數據表 -->
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
</beans>
一旦在Spring的IoC容器中配置了SessionFactory Bean,它將隨應用的啓動而加載,並能夠充分利用IoC容器的功能,將SessionFactory Bean注入任何Bean,好比DAO組件。一旦DAO組件得到了SessionFactory Bean的引用,就能夠完成實際的數據庫訪問。
固然,Spring也支持訪問容器數據源。若是須要使用容器數據源,可將數據源Bean修改爲以下配置:
<!-- 此處配置JNDI數據源 -->
<bean id="myDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<!-- 指定數據源的JNDI -->
<value>java:comp/env/jdbc/myds</value>
</property>
</bean>
可見,以聲明式的方式管理SessionFactory實例,可讓應用在不一樣數據源之間切換。若是應用更換數據庫等持久層資源,只需對配置文件進行簡單修改便可。
提示:以聲明式的方式管理SessionFactory,很是相似於早期將數據庫服務的相關信息放在web.xml文件中進行配置。這種方式是爲了提供更好的適應性,當持久層服務須要更改時,應用代碼無須任何改變。
HibernateTemplate提供持久層訪問模板,使用HibernateTemplate無須實現特定接口,它只須要提供一個SessionFactory的引用就可執行持久化操做。SessionFactory對象既可經過構造參數傳入,也可經過設值方式傳入。HibernateTemplate提供以下3個構造函數:
● HibernateTemplate()。
● HibernateTemplate(org.hibernate.SessionFactory sessionFactory)。
● HibernateTemplate(org.hibernate.SessionFactory sessionFactory, boolean allowCreate)。
第一個構造函數,構造一個默認的HibernateTemplate實例。所以,使用Hibernate- Template實例以前,還必須使用方法setSessionFactory(SessionFactory sessionFactory)來爲HibernateTemplate傳入SessionFactory的引用。
第二個構造函數,在構造時已經傳入SessionFactory引用。
第三個構造函數,其boolean型參數代表,若是當前線程已經存在一個非事務性的Session,是否直接返回此非事務性的Session。
在Web應用中,一般啓動時自動加載ApplicationContext,SessionFactory和DAO對象都處在Spring上下文管理下,所以無須在代碼中顯式設置,可採用依賴注入完成Session- Factory和DAO的解耦,依賴關係經過配置文件來設置,以下所示:
<?xml version="1.0" encoding="GBK"?>
<!-- beans是Spring配置文件的根元素,而且指定了Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 定義數據源Bean,使用C3P0數據源實現 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<!-- 指定鏈接數據庫的驅動 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<!-- 指定鏈接數據庫的URL -->
<property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
<!-- 指定鏈接數據庫的用戶名 -->
<property name="user" value="root"/>
<!-- 指定鏈接數據庫的密碼 -->
<property name="password" value="32147"/>
<!-- 指定鏈接數據庫鏈接池的最大鏈接數 -->
<property name="maxPoolSize" value="40"/>
<!-- 指定鏈接數據庫鏈接池的最小鏈接數 -->
<property name="minPoolSize" value="1"/>
<!-- 指定鏈接數據庫鏈接池的初始化鏈接數 -->
<property name="initialPoolSize" value="1"/>
<!-- 指定鏈接數據庫鏈接池的鏈接最大空閒時間 -->
<property name="maxIdleTime" value="20"/>
</bean>
<!-- 定義Hibernate的SessionFactory Bean -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.
LocalSessionFactoryBean">
<!-- 依賴注入數據源,注入的正是上文中定義的dataSource -->
<property name="dataSource" ref="dataSource"/>
<!-- mappingResources屬性用來列出所有映射文件 -->
<property name="mappingResources">
<list>
<!-- 如下用來列出全部的PO映射文件 -->
<value>lee/Person.hbm.xml</value>
</list>
</property>
<!-- 定義Hibernate的SessionFactory屬性 -->
<property name="hibernateProperties">
<props>
<!-- 指定Hibernate的鏈接方言 -->
<prop key="hibernate.dialect">org.hibernate.dialect.
MySQLDialect</prop>
<!-- 指定啓動應用時,是否根據Hibernate映射文件建立數據表 -->
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 配置Person持久化類的DAO bean -->
<bean id="personDao" class="lee.PersonDaoImpl">
<!-- 採用依賴注入來傳入SessionFactory的引用 -->
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
</beans>
在PersonDao組件中,全部的持久化操做都經過HibernateTemplate實例完成,而HibernateTemplate操做數據庫很是簡潔,大部分CRUD操做均可經過一行代碼解決問題。下面介紹如何經過HibernateTemplate進行持久層訪問。
HibernateTemplate提供了很是多的經常使用方法來完成基本的操做,好比一般的增長、刪除、修改、查詢等操做,Spring 2.0更增長了對命名SQL查詢的支持,也增長了對分頁的支持。大部分狀況下,使用Hibernate的常規用法,就可完成大多數DAO對象的CRUD操做。下面是HibernateTemplate的經常使用方法簡介:
● void delete(Object entity),刪除指定持久化實例。
● deleteAll(Collection entities),刪除集合內所有持久化類實例。
● find(String queryString),根據HQL查詢字符串來返回實例集合。
● findByNamedQuery(String queryName),根據命名查詢返回實例集合。
● get(Class entityClass, Serializable id),根據主鍵加載特定持久化類的實例。
● save(Object entity),保存新的實例。
● saveOrUpdate(Object entity),根據實例狀態,選擇保存或者更新。
● update(Object entity),更新實例的狀態,要求entity是持久狀態。
● setMaxResults(int maxResults),設置分頁的大小。
下面是一個完整DAO類的源代碼:
public class PersonDaoImpl implements PersonDao
{
//執行持久化操做的HibernateTemplate實例
private HibernateTemplate ht = null;
private SessionFactory sessionFactory;
//該DAO組件持久化操做所需的SessionFactory對象
public void setSessionFactory(SessionFactory sessionFactory)
{
this.sessionFactory = sessionFactory;
}
//用於根據SessionFactory實例返回HibernateTemplate實例的方法
private HibernateTemplate getHibernateTemplate()
{
if (ht == null)
{
ht = new HibernateTemplate(sessionFactory);
}
return ht;
}
/**
* 加載人實例
* @param id 須要加載的Person實例的主鍵值
* @return 返回加載的Person實例
*/
public Person get(int id)
{
return (Person)getHibernateTemplate().get(Person.class, new
Integer(id));
}
/**
* 保存人實例
* @param person 須要保存的Person實例
*/
public void save(Person person)
{
getHibernateTemplate().save(person);
}
/**
* 修改Person實例
* @param person 須要修改的Person實例
*/
public void update(Person person)
{
getHibernateTemplate().update(person);
}
/**
* 刪除Person實例
* @param id 須要刪除的Person的id
*/
public void delete(int id)
{
getHibernateTemplate().delete(getHibernateTemplate().get(Person.
class,new Integer(id)));
}
/**
* 刪除Person實例
* @param person 須要刪除的Person實例
*/
public void delete(Person person)
{
getHibernateTemplate().delete(person);
}
/**
* 根據用戶名查找Person
* @param name 用戶名
* @return 用戶名對應的所有用戶
*/
public List findByName(String name)
{
return getHibernateTemplate().find("from Person p where p.name
like ?" , name);
}
/**
* 返回所有的Person實例
* @return 所有的Person實例
*/
public List findAllPerson()
{
return getHibernateTemplate().find("from Person ");
}
}
經過上面實現DAO組件的代碼能夠看出,經過HibernateTemplate進行持久層訪問的代碼如此清晰,大部分CRUD操做一行代碼便可完成,徹底無須Hibernate訪問那些繁瑣的步驟。並且,一旦DAO組件得到了SessionFactory的引用,便可很輕易地建立HibernateTemplate實例。
提示:HibernateTemplate是Spring衆多模板工具類之一,Spring正是經過這種簡便地封裝,完成了開發中大量須要重複執行的工做。
posted @ 2009-07-19 10:24 jadmin 閱讀(4) 評論(0) 編輯
Struts的plug-in配置部分明確指出,Spring的配置文件有兩個:applicationContext.xml和action-servlet.xml。其實,徹底能夠使用一個配置文件。一般,習慣將Action Bean配置在控制器的context內。action-servlet.xml用於配置表現層上下文,其詳細配置信息以下:
<?xml version="1.0" encoding="gb2312"?>
<!-- 指定Spring配置文件的根元素,以及對應的Schame信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 每一個request請求產生一個新實例,將全部該請求的做用域配置成request -->
<bean name="/login" class="lee.LoginAction" scope="request">
<property name="vb" ref="vb"/>
</bean>
</beans>
由於每次請求都應該啓動新的Action處理用戶請求,所以,應將Action的做用域配置成Request。
注意:ActionServlet轉發請求時,是根據Bean的name屬性,而不是id屬性。所以,此處肯定的name屬性與Struts的action屬性相同。
applicationContext.xml只有一個bean配置,即vb bean。其詳細配置以下:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring 配置文件的根元素,以及對應的Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置ValidBean實例 -->
<bean id="vb" class="lee.ValidBeanImpl"/>
</beans>
ValidBeanImpl是一個業務邏輯bean,本示例程序中僅做簡單的判斷,ValidBeanImpl的源代碼以下:
//面向接口編程,實現ValidBean接口
public class ValidBeanImpl implements ValidBean
{
//根據輸入的用戶名和密碼判斷是否有效
public boolean valid(String username,String pass)
{
//有效,返回true
if (username.equals("scott") && pass.equals("tiger"))
{
return true;
}
return false;
}
}
注意:上面的業務邏輯組件很是簡單,它只是一個示意。若是是真實的應用,業務邏輯組件應該經過DAO組件來實現業務邏輯方法。
應用的業務邏輯控制器,Action則負責調用業務邏輯組件的方法,並根據業務邏輯組件方法的返回值,肯定如何響應用戶請求。下面是該示例應用控制器的代碼:
//業務控制器繼承Action
public class LoginAction extends Action
{
//action控制器將調用的業務邏輯組件
private ValidBean vb;
//依賴注入業務邏輯組件的setter方法
public void setVb(ValidBean vb)
{
this.vb = vb;
}
//必須重寫該核心方法,該方法actionForm將表單的請求參數封裝成值對象
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)throws
Exception
{
//form由ActionServlet轉發請求時建立,封裝了全部的請求參數
LoginForm loginForm = (LoginForm)form;
//獲取username請求參數
String username = loginForm.getUsername();
//獲取pass請求參數
String pass = loginForm.getPass();
//下面爲服務器端的數據校驗
String errMsg = "";
//判斷用戶名不能爲空
if (username == null || username.equals(""))
{
errMsg += "您的用戶名丟失或沒有輸入,請從新輸入";
}
//判斷密碼不能爲空
else if(pass == null || pass.equals(""))
{
errMsg += "您的密碼丟失或沒有輸入,請從新輸入";
}
//若是用戶名和密碼不爲空,才調用業務邏輯組件
else
{
//vb是業務邏輯組件,由容器注入
if (vb.valid(username,pass))
{
return mapping.findForward("welcome");
}
else
{
errMsg = "您的用戶名和密碼不匹配";
}
}
//判斷是否生成了錯誤信息
if (errMsg != null && !errMsg.equals(""))
{
//若是有錯誤信息,將錯誤信息保存在request裏,並跳轉到input對應的
forward對象
request.setAttribute("err" , errMsg);
return mapping.findForward("input");
}
else
{
//若是沒有錯誤信息,跳轉到welcome對應的forward對象
return mapping.findForward("welcome");
}
}
}
在本應用中,使用了Struts的客戶端數據校驗,讓Action繼承ValidatorActionForm便可。ActionForm的代碼很是簡單,此處再也不贅述。
爲了完成數據校驗,還應該編寫數據校驗規則文件。在struts-config.xml文件的尾部,另有一個plug-in用來加載校驗文件,其中validator-rules.xml文件位於struts壓縮包的lib下,直接複製過來便可使用,而validator.xml必須本身編寫,validator.xml文件以下:
<?xml version="1.0" encoding="GBK"?>
<!-- 驗證規則文件的文件頭,包括DTD等信息 -->
<!DOCTYPE form-validation PUBLIC
"-//Apache Software Foundation//DTD Commons Validator Rules
Configuration 1.1.3//EN"
"http://jakarta.apache.org/commons/dtds/validator_1_1_3.dtd">
<!-- 驗證文件的根元素 -->
<form-validation>
<!-- 全部須要驗證的form都放在formset裏 -->
<formset>
<!-- 須要驗證的form名,該名與struts裏配置的名相同 -->
<form name="loginForm">
<!-- 指定該form的username域必須知足的規則:必填、模式匹配 -->
<field property="username" depends="required,mask">
<arg key="loginForm.username" position="0"/>
<var>
<!-- 肯定匹配模式的正則表達式 -->
<var-name>mask</var-name>
<var-value>^[a-zA-Z]+$</var-value>
</var>
</field>
<!-- 指定該form的pass域必須知足的規則:必填 -->
<field property="pass" depends="required">
<msg name="required" key="pass.required"/>
<arg key="loginForm.pass" position="0"/>
</field>
</form>
</formset>
</form-validation>
上面示例程序的結構很是清晰:表現層組件(Action)配置在action-servlet.xml文件中,而業務邏輯層組件(vb)配置在applicationContext.xml文件中,若是應用中有DAO組件,將DAO組件配置在dao-context.xml文件中。將3個文件放在plug-in元素裏一塊兒加載。
DelegatingRequestProcessor會將請求轉發到Action,該Action已經處於IoC容器管理之下,所以,能夠方便地訪問容器中的其餘Bean。
經過配置文件能夠看出,Action根本無須type屬性,即struts-config.xml中Action根本沒有實例化過,DelegatingRequestProcessor將請求轉發給Spring容器中的同名Bean。這種轉發的時機很是早,避免了建立struts-config.xml配置文件中的Action,於是性能很是好。
圖6.3是採用這種整合策略的執行效果。
使用DelegatingRequestProcessor簡單方便,但有一個缺點,RequestProcessor是Struts的一個擴展點,也許應用程序自己就須要擴展RequestProcessor,而DelegatingRequest- Processor已經使用了這個擴展點。
爲了從新利用Struts的RequestProcessor這個擴展點,有兩個作法:
● 應用程序的RequestProcessor再也不繼承Struts的RequestProcessor,改成繼承DelegatingRequestProcessor。
● 使用DelegatingActionProxy。
前者經常有一些未知的風險,然後者是Spring推薦的整合策略。使用Delegating- ActionProxy與DelegatingRequestProcessor的目的都只有一個,將請求轉發給Spring管理的Bean。
DelegatingRequestProcessor直接替換了原有的RequestProcessor,在請求轉發給action以前,轉發給Spring管理的Bean;而DelegatingActionProxy則被配置成Struts的Action,即全部的請求先被ActionServlet截獲,請求被轉發到對應的Action,而action的實現類全都是DelegatingActionProxy,DelegatingActionProxy再將請求轉發給Spring容器的Action。
能夠看出,使用DelegatingActionProxy比使用DelegatingRequestProcessor要晚一步轉發到Spring的context。但經過這種方式能夠避免佔用擴展點。
與使用DelegatingRequestProcessor相比,使用DelegatingActionProxy僅須要去掉controller配置元素,並將全部的action實現類改成DelegatingActionProxy便可。詳細的配置文件以下:
<!-- XML文件的版本和編碼集 -->
<?xml version="1.0" encoding="gb2312"?>
<!-- struts配置文件的文件頭,包括DTD等信息 -->
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"
"http://struts.apache.org/dtds/struts-config_1_2.dtd">
<!-- struts配置文件的根元素 -->
<struts-config>
<!-- 配置formbean,全部的formbean都放在form-beans元素裏定義 -->
<form-beans>
<!-- 定義了一個formbean,肯定formbean名和實現類 -->
<form-bean name="loginForm" type="lee.LoginForm"/>
</form-beans>
<!-- 定義action部分,全部的action都放在action-mapping元素裏定義 -->
<action-mappings>
<!-- 這裏只定義了一個action。必須配置action的type元素爲
DelegatingActionProxy -->
<action path="/login" type="org.springframework.web.struts.
DelegatingActionProxy"
name="loginForm" scope="request" validate="true" input=
"/login.jsp" >
<!-- 定義action內的兩個局部forward元素 -->
<forward name="input" path="/login.jsp"/>
<forward name="welcome" path="/welcome.html"/>
</action>
</action-mappings>
<!-- 加載國際化的資源包 -->
<message-resources parameter="mess"/>
<!-- 裝載驗證的資源文件 -->
<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-property property="pathnames" value="/WEB-INF/validator-
rules.xml,/WEB-INF/validation.xml" />
<set-property property="stopOnFirstError" value="true"/>
</plug-in>
<!-- 裝載Spring配置文件,隨應用啓動建立ApplicationContext實例 -->
<plug-in className="org.springframework.web.struts. ContextLoaderPlugIn">
<set-property property="contextConfigLocation"
value="/WEB-INF/applicationContext.xml,
/WEB-INF/action-servlet.xml"/>
</plug-in>
</struts-config>
DelegatingActionProxy接收ActionServlet轉發過來的請求,而後轉發給Application- Context管理的Bean,這是典型的鏈式處理。
經過配置文件能夠看出,struts-config.xml文件中配置了大量DelegatingActionProxy實例,Spring容器中也配置了同名的Action。即Struts的業務控制器分紅了兩個部分:第一個部分是Spring的DelegatingActionProxy,這個部分沒有實際意義,僅僅完成轉發;第二個部分是用戶的Action實現類,該實現類負責真實的處理。
這種策略的性能比前一種策略的效果要差一些,由於須要多建立一個Delegating- ActionProxy實例。並且,J2EE應用中Action很是多,這將致使大量建立DelegatingActionProxy實例,使用一次以後,等待垃圾回收機制回收——這對性能的影響不可避免。
圖6.4是DelegatingActionProxy的執行效果。
注意:使用DelegatingActionProxy的整合策略,可避免佔用Struts的RequestProcessor擴展點,但下降了整合性能。
前面已經介紹了,Spring與Struts的整合還有一種策略,讓Struts的Action顯式獲取Spring容器中的Bean。在這種策略下,Struts的Action不接受IoC容器管理,Action的代碼與Spring API部分耦合,形成代碼污染。這種策略也有其好處:代碼的可讀性很是強,Action的代碼中顯式調用業務邏輯組件,而無須等待容器注入。
Action中訪問ApplicationContext有兩種方法:
● 利用WebApplicationContextUtils工具類。
● 利用ActionSupport支持類。
經過WebApplicationContextUtils,能夠顯式得到Spring容器的引用(請參閱6.4.1節的內容),而ActionSupport類則提供了一個更簡單的方法getWebApplicationContext(),該方法可直接獲取Spring容器的引用。
所謂ActionSupport類,是指Spring提供了系列擴展。Spring擴展了Struts的Action,在Struts的Action後加上Support後綴,Spring擴展的Action有以下幾個:
● ActionSupport。
● DispatchActionSupport。
● LookupDispatchActionSupport。
● MappingDispatchActionSupport。
下面的示例將展現這種整合策略,在這種整合策略下,Struts的Action改成繼承Spring擴展後的Action,下面是應用的Action代碼:
//新的業務控制器,繼承Spring的ActionSupport類
public class LoginAction extends ActionSupport
{
//依然將ValidBean做爲成員變量
private ValidBean vb;
//構造器,注意:不可在構造器中調用getWebApplicationContext()方法
public LoginAction()
{
}
//完成ValidBean的初始化
public ValidBean getVb()
{
return(ValidBean)getWebApplicationContext().getBean("vb");
}
//必須重寫該核心方法,該方法actionForm將表單的請求參數封裝成值對象
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)throws
Exception
{
//form由ActionServlet轉發請求時建立,封裝了全部的請求參數
LoginForm loginForm = (LoginForm)form;
//獲取username請求參數
String username = loginForm.getUsername();
//獲取pass請求參數
String pass = loginForm.getPass();
//下面爲服務器端的數據校驗
String errMsg = "";
//判斷用戶名不能爲空
if (username == null || username.equals(""))
{
errMsg += "您的用戶名丟失或沒有輸入,請從新輸入";
}
//判斷密碼不能爲空
else if(pass == null || pass.equals(""))
{
errMsg += "您的密碼丟失或沒有輸入,請從新輸入";
}
//若是用戶名和密碼不爲空,才調用業務邏輯組件
else
{
//vb是業務邏輯組件,經過上面的初始化方法得到
if (getVb().valid(username,pass))
{
return mapping.findForward("welcome");
}
else
{
errMsg = "您的用戶名和密碼不匹配";
}
}
//判斷是否生成了錯誤信息
if (errMsg != null && !errMsg.equals(""))
{
//若是有錯誤信息,將錯誤信息保存在request裏,並跳轉到input對應的
//forward對象
request.setAttribute("err" , errMsg);
return mapping.findForward("input");
}
else
{
//若是沒有錯誤信息,跳轉到welcome對應的forward對象
return mapping.findForward("welcome");
}
}
}
在上面的Action代碼中,Action顯式獲取容器中的業務邏輯組件,而不是依靠Spring容器的依賴注入。在這種整合策略下,表現層的控制器組件再也不接受IoC容器管理。所以,沒有控制器上下文,應將原有的action-servlet.xml文件刪除,並修改plug-in元素,不要加載該文件。還要修改Action配置,將Action配置的type元素修改爲實際的處理類。這 種整合策略也有一個好處:代碼的可讀性更強,對傳統Struts應用開發的改變很小,容易使用。
將該Action部署在struts-config.xml中,Struts將負責建立該Action。struts-config.xml文件的源代碼以下:
<!-- XML文件的版本和編碼集 -->
<?xml version="1.0" encoding="gb2312"?>
<!-- Struts配置文件的文件頭,包括DTD等信息 -->
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"
"http://struts.apache.org/dtds/struts-config_1_2.dtd">
<!-- struts配置文件的根元素 -->
<struts-config>
<!-- 配置formbean,全部的formbean都放在form-beans元素裏定義 -->
<form-beans>
<!-- 定義了一個formbean,肯定formbean名和實現類 -->
<form-bean name="loginForm" type="lee.LoginForm"/>
</form-beans>
<!-- 定義action部分,全部的action都放在action-mapping元素裏定義 -->
<action-mappings>
<!-- 這裏只定義了一個action。action的類型爲ActionSupport的子類 -->
<action path="/login" type="type="lee.LoginAction"
name="loginForm" scope="request" validate="true" input=
"/login.jsp" >
<!-- 定義action內的兩個局部forward元素 -->
<forward name="input" path="/login.jsp"/>
<forward name="welcome" path="/welcome.html"/>
</action>
</action-mappings>
<!-- 加載國際化的資源包 -->
<message-resources parameter="mess"/>
<!-- 裝載驗證的資源文件 -->
<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-property property="pathnames" value="/WEB-INF/validator-
rules.xml,/WEB-INF/validation.xml" />
<set-property property="stopOnFirstError" value="true"/>
</plug-in>
</struts-config>
此時,Spring無須使用配置Action的配置文件,這種配置方式很是簡單。只須要業務邏輯組件的配置文件,業務邏輯組件的配置文件與前面的示例沒有任何改變。
該配置文件中的業務邏輯組件由Spring容器負責實現,而ActionSupport可以先定位Spring容器,而後得到容器的業務邏輯組件。
這種整合策略的執行效果與前面兩種整合策略的執行效果徹底相同。從代碼中分析可見,在這種整合策略下,業務控制器再次退回到Struts起初的設計。僅由strutsconfig.xml中Action充當,從而避免了像DelegatingActionProxy整合策略的性能低下,由於能夠只須要建立實際的Action實例。
注意:在這種整合策略下,Struts開發者的改變最小,最接近傳統Struts應用開發者的習慣。但這種整合策略會形成代碼污染,由於Action類必須繼承Spring的ActionSupport類。
posted @ 2009-07-19 10:23 jadmin 閱讀(1) 評論(0) 編輯
雖然Spring也提供了本身的MVC組件,但一來Spring的MVC組件過於繁瑣,二 來Struts的擁護者實在太多。所以,不少項目都會選擇使用Spring整合Struts框架。並且Spring確實能夠無縫整合Struts框架,兩者結合成一個更實際的J2EE開發平臺。
使用Spring的Web應用時,不用手動建立Spring容器,而是經過配置文件聲明式地建立Spring容器。所以,在Web應用中建立Spring容器有以下兩個方式:
● 直接在web.xml文件中配置建立Spring容器。
● 利用第三方MVC框架的擴展點,建立Spring容器。
其實第一種建立Spring容器的方式更加常見。爲了讓Spring容器隨Web應用的啓動而自動啓動,有以下兩個方法:
● 利用ServletContextListener實現。
● 採用load-on-startup Servlet實現。
Spring提供ServletContextListener的一個實現類ContextLoaderListener,該類能夠做爲Listener使用,會在建立時自動查找WEB-INF/下的applicationContext.xml文件,所以,若是隻有一個配置文件,而且文件名爲applicationContext.xml,只需在web.xml文件中增長以下配置片斷便可:
<listener>
<listener-class>org.springframework.web.context.
ContextLoaderListener</listener-class>
</listener>
若是有多個配置文件須要載入,則考慮使用<context-param>元素來肯定配置文件的文件名。ContextLoaderListener加載時,會查找名爲contextConfigLocation的參數。所以,配置context-param時,參數名字應該是contextConfigLocation。
帶多個配置文件的web.xml文件以下:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Web配置文件的根元素,以及相應的Schema信息 -->
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<!-- 肯定多個配置文件 -->
<context-param>
<!-- 參數名爲contextConfigLocation -->
<param-name>contextConfigLocation</param-name>
<!-- 多個配置文件之間以「,」隔開 -->
<param-value>/WEB-INF/daoContext.xml,/WEB-INF/
applicationContext.xml</param-value>
</context-param>
<!-- 採用listener建立ApplicationContext實例 -->
<listener>
<listener-class>org.springframework.web.context.
ContextLoaderListener</listener-class>
</listener>
</web-app>
若是沒有經過contextConfigLocation指定配置文件,Spring會自動查找application- Context.xml配置文件;若是有contextConfigLocation,則利用該參數肯定的配置文件。若是沒法找到合適的配置文件,Spring將沒法正常初始化。
Spring根據bean定義建立WebApplicationContext對象,並將其保存在web應用的ServletContext中。大部分狀況下,應用中的Bean無須感覺到ApplicationContext的存在,只要利用ApplicationContext的IoC便可。
若是須要在應用中獲取ApplicationContext實例,能夠經過以下代碼獲取:
//獲取當前Web應用的Spring容器
WebApplicationContext ctx =
WebApplicationContextUtils.getWebApplicationContext(servletContext);
除此以外,Spring提供了一個特殊的Servlet類ContextLoaderServlet。該Servlet在啓動時,會自動查找WEB-INF/下的applicationContext.xml文件。
固然,爲了讓ContextLoaderServlet隨應用的啓動而啓動,應將此Servlet配置成load-on-startup的Servlet,load-on-startup的值小一點比較合適,這樣能夠保證Application- Context更快的初始化。
若是隻有一個配置文件,而且文件名爲applicationContext.xml,在web.xml文件中增長以下一段便可:
<servlet>
<servlet-name>context</servlet-name>
<servlet-class>org.springframework.web.context.ContextLoaderServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
該Servlet用於提供「後臺」服務,主要用於建立Spring容器,無須響應客戶請求,所以無須配置servlet-mapping。
若是有多個配置文件,同樣使用<context-param>元素來肯定多個配置文件。
事實上,無論是ContextLoaderServlet,仍是ContextLoaderListener,都依賴於ContextLoader建立ApplicationContext實例。
在ContextLoader代碼的第240行,有以下代碼:
String configLocation = servletContext.getInitParameter
(CONFIG_LOCATION_PARAM);
if (configLocation != null) {
wac.setConfigLocations(StringUtils.tokenizeToStringArray
(configLocation,
ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
其中,CONFIG_LOCATION_PARAM是該類的常量,其值爲contextConfigLocation。能夠看出,ContextLoader首先檢查servletContext中是否有contextConfigLocation的參數,若是有該參數,則加載該參數指定的配置文件。
ContextLoaderServlet與ContextLoaderListener底層都依賴於ContextLoader。所以,兩者的效果幾乎沒有區別。之間的區別不是它們自己引發的,而是因爲Servlet規範,Listener比Servlet優先加載。所以,採用ContextLoaderListener建立ApplicationContext的時機更早。
固然,也能夠經過ServletContext的getAttribute方法獲取ApplicationContext。但使用WebApplicationContextUtils類更便捷,由於無須記住ApplicationContext的屬性名。即便ServletContext的WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRI- BUTE屬性沒有對應對象,WebApplicationContextUtils的getWebApplicationContext()方法將會返回空,而不會引發異常。
到底須要使用Listener,仍是使用load-on-startup Servlet來建立Spring容器呢?一般推薦使用Listener來建立Spring容器。但Listerner是Servlet 2.3以上才支持的標準,所以,必須Web容器支持Listener纔可以使用Listerner。
注意:使用Listener建立Spring容器以前,應先評估Web容器是否支持Listener標準。
還有一種狀況,利用第三方MVC框架的擴展點來建立Spring容器,好比Struts。在第2章介紹Strust框架時,知道Struts有一個擴展點PlugIn。
實際上,Spring正是利用了PlugIn這個擴展點,從而提供與Struts的整合。Spring提供了PlugIn接口的實現類org.springframework.web.struts.ContextLoaderPlugIn。這個實現類可做爲Struts的PlugIn配置,Struts框架啓動時,將自動建立Spring容器。
爲了利用Struts的PlugIn建立Spring容器,只需在Struts配置文件中增長以下片斷 便可:
<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
<set-property property="contextConfigLocation"
value="/WEB-INF/action-servlet.xml,/WEB-INF/applicationContext.
xml"/>
</plug-in>
其中,指定contextConfigLocation屬性值時,便可以指定一個Spring配置文件的位置,能夠指定多個Spring配置文件的位置。
對於一個基於B/S架構的J2EE應用而言,用戶請求老是向MVC框架的控制器請求,而當控制器攔截到用戶請求後,必須調用業務邏輯組件來處理用戶請求。此時有一個問題,控制器應該如何得到業務邏輯組件?
最容易想到的策略是,直接經過new關鍵字建立業務邏輯組件,而後調用業務邏輯組件的方法,根據業務邏輯方法的返回值肯定結果。
實際的應用中,不多見到採用上面的訪問策略,由於這是一種很是差的策略。不這樣作至少有以下3個緣由:
● 控制器直接建立業務邏輯組件,致使控制器和業務邏輯組件的耦合下降到代碼層次,不利於高層次解耦。
● 控制器不該該負責業務邏輯組件的建立,控制器只是業務邏輯組件的使用者。無須關心業務邏輯組件的實現。
● 每次建立新的業務邏輯組件將致使性能降低。
答案是採用工廠模式或服務定位器。採用服務定位器的模式,是遠程訪問的場景。在這種場景下,業務邏輯組件已經在某個容器中運行,並對外提供某種服務。控制器無須理會該業務邏輯組件的建立,直接調用便可,但在調用以前,必須先找到該服務——這就是服務定位器的概念。經典J2EE應用就是這種結構的應用。
對於輕量級的J2EE應用,工廠模式則是更實際的策略。由於輕量級的J2EE應用裏,業務邏輯組件不是EJB,一般就是一個POJO,業務邏輯組件的生成一般由工廠負責,並且工廠能夠保證該組件的實例只需一個就夠了,能夠避免重複實例化形成的系統開銷。
如圖6.2就是採用工廠模式的順序圖。
圖6.2 工廠模式順序圖
採用工廠模式,將控制器與業務邏輯組件的實現分離,從而提供更好的解耦。
在採用工廠模式的訪問策略中,全部的業務邏輯組件的建立由工廠負責,業務邏輯組件的運行也由工廠負責。而控制器只需定位工廠實例便可。
若是系統採用Spring框架,則Spring成爲最大的工廠。Spring負責業務邏輯組件的建立和生成,並可管理業務邏輯組件的生命週期。能夠如此理解,Spring是一個性能很是優秀的工廠,能夠生產出全部的實例,從業務邏輯組件,到持久層組件,甚至控制器。
如今的問題是,控制器如何訪問到Spring容器中的業務邏輯組件?爲了讓Action訪 問Spring的業務邏輯組件,有兩種策略:
● Spring管理控制器,並利用依賴注入爲控制器注入業務邏輯組件。
● 控制器顯式定位Spring工廠,也就是Spring的容器ApplicationContext實例,並從工廠中獲取業務邏輯組件實例的引用。
第一種策略,充分利用Spring的IoC特性,是最優秀的解耦策略。但不可避免帶來一些不足之處,概括起來主要有以下不足之處:
● Spring管理Action,必須將全部的Action配置在Spring容器中,而struts-config.xml文件中的配置也不會減小,致使配置文件大量增長。
● Action的業務邏輯組件接收容器注入,將致使代碼的可讀性下降。
整體而言,這種整合策略是利大於弊。
第二種策略,與前面介紹的工廠模式並無太大的不一樣。區別是Spring容器充當了業務邏輯組件的工廠。控制器負責定位Spring容器,一般Spring容器訪問容器中的業務邏輯組件。這種策略是一種折衷,下降了解耦,但提升了程序的可讀性。
Spring徹底支持這兩種策略,既可讓Spring容器管理控制器,也可讓控制器顯式定位Spring容器中的業務邏輯組件。
這裏介紹的是第一種整合策略:讓Spring管理Struts的Action。那麼一樣有一個問題,讓Spring管理Struts的Action時,客戶端的HTTP 請求如何轉向Spring容器中的Action?
當使用Struts做爲MVC框架時,客戶端的HTTP請求都是直接向ActionServlet請求的,所以關鍵就是讓ActionServlet將請求轉發給Spring容器中的Action。這很明顯能夠利用Spring的另外一個擴展點:經過擴展RequestProcessor完成,使用擴展的RequestProcessor替換Struts的RequestProcessor。
Spring完成了這種擴展,Spring提供的DelegatingRequestProcessor繼承Request- Processor。爲了讓Struts使用DelegatingRequestProcessor,還須要在struts-config.xml文件中增長以下一行:
//使用spring的RequestProcessor替換struts原有的RequestProcessor
<controller processorClass="org.springframework.web.struts.
DelegatingRequestProcessor"/>
完成這個設置後,Struts會將截獲到的用戶請求轉發到Spring context下的bean,根據bean的name屬性來匹配。而Struts中的action配置則無須配置class屬性,即便配置了class屬性也沒有任何用處,即下面兩行配置是徹底同樣的:
//配置struts action時,指定了實現類
<action path="/user" type="lee.UserAction"/>
//配置struts action時,沒有指定實現類
<action path="/user"/>
下面的示例程序在上一個示例程序的基礎上稍做修改,增長了客戶端驗證和程序國際化部分。也調用了Spring的業務bean來驗證登陸。先看修改後的struts-config.xml文件:
<!-- XML文件版本,編碼集 -->
<?xml version="1.0" encoding="gb2312"?>
<!-- Struts配置文件的文件頭,包括DTD等信息 -->
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"
"http://struts.apache.org/dtds/struts-config_1_2.dtd">
<!-- struts配置文件的根元素 -->
<struts-config>
<!-- 配置formbean,全部的formbean都放在form-beans元素裏定義 -->
<form-beans>
<!-- 定義了一個formbean,肯定formbean名和實現類 -->
<form-bean name="loginForm" type="lee.LoginForm"/>
</form-beans>
<!-- 定義action部分,全部的action都放在action-mapping元素裏定義 -->
<action-mappings>
<!-- 這裏只定義了一個action。並且沒有指定該action的type元素 -->
<action path="/login" name="loginForm"
scope="request" validate="true" input="/login.jsp" >
<!-- 定義action內的兩個局部forward元素 -->
<forward name="input" path="/login.jsp"/>
<forward name="welcome" path="/welcome.html"/>
</action>
</action-mappings>
<!-- 使用DelegatingRequestProcessor替換RequestProcessor -->
<controller processorClass="org.springframework.web.struts.
DelegatingRequestProcessor"/>
<!-- 加載國際化的資源包 -->
<message-resources parameter="mess"/>
<!-- 裝載驗證的資源文件 -->
<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-property property="pathnames" value="/WEB-INF/validator-
rules.xml,/WEB-INF/validation.xml" />
<set-property property="stopOnFirstError" value="true"/>
</plug-in>
<!-- 裝載Spring配置文件,隨應用的啓動建立ApplicationContext實例 -->
<plug-in className="org.springframework.web.struts.
ContextLoaderPlugIn">
<set-property property="contextConfigLocation"
value="/WEB-INF/applicationContext.xml,
/WEB-INF/action-servlet.xml"/>
</plug-in>
</struts-config>
修改後的struts-config.xml文件,增長加載國際化資源文件。配置Struts的action不須要class屬性,完成了ApplicationContext的建立。
而後考慮web.xml文件的配置,在web.xml文件中必須配置Struts框架的加載。除此以外,由於使用了Spring管理Struts的Action,而Action是隨HTTP請求啓動的,所以,應將Action的做用域配置成Request,爲了使用Request做用域,必須在web.xml文件中增長適當的配置。
下面是web.xml文件的代碼:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Web配置文件的根元素,以及對應的Schema信息 -->
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<!-- 定義一個Filter,該Filter是使用Request做用域的基礎 -->
<filter>
<filter-name>requestContextFilter</filter-name>
<filter-class>org.springframework.web.filter.
RequestContextFilter </filter-class>
</filter>
<!-- 定義filter-mapping,讓上面的Filter過濾全部的用戶請求 -->
<filter-mapping>
<filter-name>requestContextFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 定義Struts的核心Servlet -->
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet
</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<!-- 定義Struts的核心Servlet攔截全部*.do請求 -->
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<!-- 關於Struts標籤庫的配置 -->
<jsp-config>
<!-- 配置bean標籤 -->
<taglib>
<taglib-uri>/tags/struts-bean</taglib-uri>
<taglib-location>/WEB-INF/struts-bean.tld</taglib-location>
</taglib>
<!-- 配置html標籤 -->
<taglib>
<taglib-uri>/tags/struts-html</taglib-uri>
<taglib-location>/WEB-INF/struts-html.tld</taglib-location>
</taglib>
<!-- 配置logic標籤 -->
<taglib>
<taglib-uri>/tags/struts-logic</taglib-uri>
<taglib-location>/WEB-INF/struts-logic.tld</taglib-location>
</taglib>
</jsp-config>
</web-app>
posted @ 2009-07-19 10:22 jadmin 閱讀(2) 評論(0) 編輯
雖然在上面的配置片斷中,僅僅配置了JDBC局部事務管理器、Hibernate局部事務管理器、JDBC全局事務管理器等。但Spring支持大部分持久化策略的事務管理器。
不論採用何種持久化策略,Spring都提供了一致的事務抽象,所以,應用開發者能在任何環境下,使用一致的編程模型。無須更改代碼,應用就可在不一樣的事務管理策略中切換。Spring同時支持聲明式事務管理和編程式事務管理。
使用編程式事務管理,開發者使用的是Spring事務抽象,而無須使用任何具體的底層事務API。Spring的事務管理將代碼從底層具體的事務API中抽象出來,該抽象能夠使用在任何底層事務基礎之上。
使用聲明式策略,開發者一般書寫不多的事務管理代碼,所以,不依賴Spring或任何其餘事務API。Spring的聲明式事務無須任何額外的容器支持,Spring容器自己管理聲明式事務。使用聲明事務策略,無須在業務代碼中書寫任何事務代碼,可讓開發者更好地專一於業務邏輯的實現。Spring管理的事務支持多個事務資源的跨越,但沒法支持跨越遠程調用的事務上下文傳播。
Spring同時支持編程式事務策略和聲明式事務策略,大部分時候,都推薦採用聲明式事務策略,使用聲明事務策略的優點十分明顯:
● 聲明式事務能大大下降開發者的代碼書寫量。並且聲明式事務幾乎不須要影響應用的代碼。所以,不管底層事務策略如何變化,應用程序無須任何改變。
● 應用程序代碼無須任何事務處理代碼,能夠更專一於業務邏輯的實現。
● Spring則可對任何POJO的方法提供事務管理,並且Spring的聲明式事務管理無須容器的支持,可在任何環境下使用。
● EJB的CMT沒法提供聲明式回滾規則。而經過配置文件,Spring可指定事務在遇到特定異常時自動回滾。Spring不只可在代碼中使用setRollbackOnly回滾事務,也可在配置文件中配置回滾規則。
● 因爲Spring採用AOP的方式管理事務,所以,能夠在事務回滾動做中插入用戶本身的動做,而不只僅是執行系統默認的回滾。
提示:本節不打算全面介紹Spring的各類事務策略,所以本節不會介紹編程式事務。若是讀者須要更全面瞭解Spring事務的相關方面,請參閱筆者所著的《Spring2.0寶典》 一書。
對於採用聲明式事務策略,能夠使用TransactionProxyFactoryBean來配置事務代理Bean。正如它的類名所暗示的,它是一個工廠Bean,工廠Bean用於生成一系列的Bean實例,這一系列的Bean實例都是Proxy。
可能讀者已經想到了,既然TransactionProxyFactoryBean產生的是代理Bean,可見這種事務代理正是基於Spring AOP組件的。配置TransactionProxyFactoryBean時,同樣須要指定目標Bean。
每一個TransactionProxyFactoryBean爲一個目標Bean生成事務代理,事務代理的方法改寫了目標Bean的方法,就是在目標Bean的方法執行以前加入開始事務,在目標Bean的方法正常結束以前提交事務,若是遇到特定異常則回滾事務。
TransactionProxyFactoryBean建立事務代理時,須要瞭解當前事務所處的環境,該環境屬性經過PlatformTransactionManager實例傳入,而相關事務傳入規則在TransactionProxy- FactoryBean的定義中給出。
下面給出聲明式事務配置文件的完整代碼:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 定義數據源Bean,使用C3P0數據源實現 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.
ComboPooledDataSource" destroy-method="close">
<!-- 指定鏈接數據庫的驅動 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<!-- 指定鏈接數據庫的URL -->
<property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
<!-- 指定鏈接數據庫的用戶名 -->
<property name="user" value="root"/>
<!-- 指定鏈接數據庫的密碼 -->
<property name="password" value="32147"/>
<!-- 指定鏈接數據庫鏈接池的最大鏈接數 -->
<property name="maxPoolSize" value="40"/>
<!-- 指定鏈接數據庫鏈接池的最小鏈接數 -->
<property name="minPoolSize" value="1"/>
<!-- 指定鏈接數據庫鏈接池的初始化鏈接數 -->
<property name="initialPoolSize" value="1"/>
<!-- 指定鏈接數據庫鏈接池的鏈接最大空閒時間 -->
<property name="maxIdleTime" value="20"/>
</bean>
<!-- 定義Hibernate的SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.
LocalSessionFactoryBean">
<!-- 依賴注入SessionFactory所需的數據源,正是上文定義的dataSource -->
<property name="dataSource" <ref="dataSource"/>
<!-- mappingResources屬性用來列出所有映射文件 -->
<property name="mappingResources">
<list>
<!-- 如下用來列出全部的PO映射文件 -->
<value>lee/Person.hbm.xml</value>
</list>
</property>
<!-- 定義Hibernate的SessionFactory屬性 -->
<property name="hibernateProperties">
<props>
<!-- 指定Hibernate的鏈接方言 -->
<prop key="hibernate.dialect">org.hibernate.dialect.
MySQLDialect</prop>
<!-- 是否根據Hiberante映射建立數據表時,選擇create、update、
create-drop -->
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 配置DAO Bean,該Bean將做爲目標Bean使用 -->
<bean id="personDAOTarget" class="lee.PersonDaoImpl">
<!-- 採用依賴注入來傳入SessionFactory的引用 -->
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<!-- 配置Hibernate的事務管理器 -->
<!-- 使用HibernateTransactionManager類,該類實現PlatformTransactionManager
接口,針對採用Hibernate持久化鏈接的特定實現 -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.
HibernateTransactionManager">
<!-- HibernateTransactionManager Bean,它須要依賴注入一個SessionFactory
Bean的引用 -->
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<!-- 配置personDAOTarget Bean的事務代理 -->
<bean id="personDAO"
class="org.springframework.transaction.interceptor.
TransactionProxyFactoryBean">
<!-- 依賴注入PlatformTransactionManager的bean引用,此處使用
Hibernate的bean -->
<!-- 局部事務器,所以transactionManager 傳入Hibernate事務管理器的
引用 -->
<property name="transactionManager" ref="transactionManager"/>
<!-- 須要生成代理的目標bean -->
<property name="target" ref="personDAOTarget"/>
<!-- 指定事務屬性 -->
<property name="transactionAttributes">
<props>
<!-- 如下部分爲定義事務回滾規則 -->
<prop key="insert*">PROPAGATION_REQUIRED,
-MyCheckedException</prop>
<prop key="update*">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
</bean>
</beans>
在上面的定義文件中,沒有對DAO對象採用Service層包裝。一般狀況下,DAO層上應有一層Service層。事務代理則以Service層Bean爲目標Bean。此處爲了簡化配置,TransactionProxyFactoryBean直接以DAO bean做爲目標bean,這一點不會影響事務代理的生成。
事務回滾規則部分定義了三個回滾規則:
第一個回滾規則表示全部以insert開始的方法,都應該知足該事務規則。PROPAGATION_REQUIRED事務傳播規則指明,該方法必須處於事務環境中,若是當前執行線程已處於事務環境下,則直接執行;不然,啓動新的事務而後執行該方法。該規則還指定,若是方法拋出MyCheckedException的實例及其子類的實例,則強制回滾。MyCheckedException前的「-」表示強制回滾;「+」則表示強制提交,即某些狀況下,即便拋出異常也強制提交;
第二個回滾規則表示全部以update開頭的方法,都遵照PROPAGATION_REQUIRED的事務傳播規則;
第三個回滾規則表示除前面規定的方法外,其餘全部方法都採用PROPAGATION_ REQUIRED事務傳播規則,並且只讀。
常見的事務傳播規則有以下幾個:
● PROPAGATION_MANDATORY,要求調用該方法的線程必須處於事務環境中,不然拋出異常。
● PROPAGATION_NESTED,若是執行該方法的線程已處於事務環境下,依然啓動新的事務,方法在嵌套的事務裏執行。若是執行該方法的線程序並未處於事務中,也啓動新的事務,而後執行該方法,此時與PROPAGATION_REQUIRED相同。
● PROPAGATION_NEVER,不容許調用該方法的線程處於事務環境下,若是調用該方法的線程處於事務環境下,則拋出異常。
● PROPAGATION_NOT_SUPPORTED,若是調用該方法的線程處在事務中,則先暫停當前事務,而後執行該方法。
● PROPAGATION_REQUIRED,要求在事務環境中執行該方法,若是當前執行線程已處於事務中,則直接調用;若是當前執行線程不處於事務中,則啓動新的事務後執行該方法。
● PROPAGATION_REQUIRES_NEW,該方法要求有一個線程在新的事務環境中執行,若是當前執行線程已處於事務中,先暫停當前事務,啓動新的事務後執行該方法;若是當前調用線程不處於事務中,則啓動新的事務後執行該方法。
● PROPAGATION_SUPPORTS,若是當前執行線程處於事務中,則使用當前事務,不然不使用事務。
程序裏原來使用personDAO的地方,無須變化。由於,配置文件裏將personDAO目標Bean的id改爲personDAOTarget,爲TransactionProxyFactoryBean工廠Bean所產生的代理Bean命名爲personDAO。該代理Bean會包含原有personDAO的全部方法,並且爲這些方法增長了不一樣的事務處理規則。
程序面向PersonDaoImpl類所實現的接口編程,TransactionProxyFactoryBean生成的代理Bean也會實現TransactionProxyFactoryBean接口。所以,原有的程序中調用DAO組件的代碼無須任何改變。程序運行時,由事務代理完成原來目標Bean完成的工做。
事實上,Spring不只支持對接口的代理,整合CGLIB後,Spring甚至可對具體類生成代理。只要設置proxyTargetClass屬性爲true就能夠。若是目標Bean沒有實現任何接口,proxyTargetClass屬性默認被設爲true,此時Spring會對具體類生成代理。固然,一般建議面向接口編程,而不要面向具體的實現類編程。
仔細觀察配置文件中兩個事務代理Bean的配置時,發現兩個事務代理Bean的大部分配置徹底相同,若是配置文件中包含大量這樣的事務代理Bean配置,配置文件將很是臃腫。考慮到大部分事務代理Bean的配置都大同小異,能夠使用Bean繼承來簡化事務代理的配置。
正如前面部分介紹的,Bean繼承是將全部子Bean中相同的配置定義成一個模板,並將此模板Bean定義成一個抽象Bean。考慮全部事務代理Bean中,有以下部分是大體相 同的:
● 事務代理Bean所使用的事務管理器。
● 事務傳播規則。
所以,如今配置文件中定義以下的事務代理模板Bean,其配置代碼以下:
<!-- 定義全部事務代理Bean的模板 -->
<bean id="txProxyTemplate" abstract="true"
class="org.springframework.transaction.interceptor.
TransactionProxyFactoryBean">
<!-- 爲事務代理Bean注入生成代理所需的PlatformTransactionManager實例 -->
<property name="transactionManager" ref="transactionManager"/>
<!-- 定義生成事務代理通用的事務屬性 -->
<property name="transactionAttributes">
<props>
<!-- 全部的方法都應用PROPAGATION_REQUIRED的事務傳播規則 -->
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
而真正的事務代理Bean,則改成繼承上面的事務模板Bean。考慮到將目標Bean定義在Spring容器中可能增長未知的風險,所以將目標Bean定義成嵌套Bean。
<!-- 讓事務代理Bean繼承模板Bean -->
<bean id="personDAO" parent="txProxyTemplate">
<!-- 這裏採用嵌套Bean的方式來定義目標Bean,固然也能夠引用已存在的Bean -->
<property name="target">
<bean class="lee.personDAO"/>
</property>
</bean>
此時的personDAO Bean無須具體地定義事務屬性,它將在其父Bean txProxyTemplate中獲取事務定義屬性。此處採用嵌套Bean來定義目標Bean,所以,並未將目標Bean直接暴露在Spring的上下文中讓其餘模塊調用。固然,也可採用一個已經存在的Bean做爲目標Bean;子Bean的事務屬性定義,徹底可覆蓋事務代理模板裏的事務屬性定義。以下例所示:
<!-- 讓事務代理bean繼承模板Bean -->
<bean id="personDAO" parent="txProxyTemplate">
<!-- 這裏,採用引用已存在的bean的方式來定義目標Bean -->
<property name="target" ref ="personDAOTarget"/>
<!-- 覆蓋事務代理模板bean中的事務屬性定義 -->
<property name="transactionAttributes">
<props>
<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
</bean>
可見,採用Bean繼承方式定義事務代理的方式,能夠很好地簡化事務代理的配置,能夠避免配置事務代理Bean時的冗餘配置。
提示:使用Bean繼承能夠很好地簡化事務代理Bean的配置,經過將各事務代理Bean共同的配置信息提取成事務模板Bean,可讓實際的事務代理Bean的配置更加簡潔;並且,配置方式至關直觀。儘可能將目標Bean配置成嵌套Bean,這樣的方式能夠保證更好的內聚性。
若是讀者還記得前面介紹的AOP知識,應該知道還有一種更加簡潔的配置,就是利用Bean後處理器,讓Bean後處理器爲容器中其餘Bean自動建立事務代理。
回顧6.2.6節和6.2.7節,讀者可能已經想到如何自動建立代理。是的,正是經過6.2.6節和6.2.7節所給出的兩個自動代理建立類來生成事務代理。
正如前文已經提到的,使用BeanNameAutoProxyCreator和DefaultAdvisorAutoProxy- Creator來建立代理時,並不必定是建立事務代理,關鍵在於傳入的攔截器,若是傳入事務攔截器,將可自動生成事務代理。
下面是使用BeanNameAutoProxyCreator自動生成事務代理的配置文件:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素,以及相應的Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 定義數據源Bean,使用C3P0數據源實現 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.
ComboPooledDataSource" destroy-method="close">
<!-- 指定鏈接數據庫的驅動 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<!-- 指定鏈接數據庫的URL -->
<property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
<!-- 指定鏈接數據庫的用戶名 -->
<property name="user" value="root"/>
<!-- 指定鏈接數據庫的密碼 -->
<property name="password" value="32147"/>
<!-- 指定鏈接數據庫鏈接池的最大鏈接數 -->
<property name="maxPoolSize" value="40"/>
<!-- 指定鏈接數據庫鏈接池的最小鏈接數 -->
<property name="minPoolSize" value="1"/>
<!-- 指定鏈接數據庫鏈接池的初始化鏈接數 -->
<property name="initialPoolSize" value="1"/>
<!-- 指定鏈接數據庫鏈接池的鏈接最大空閒時間 -->
<property name="maxIdleTime" value="20"/>
</bean>
<!-- 使用JDBC的局部事務策略 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSource-
TransactionManager">
<!-- 爲事務管理器注入所需的數據源Bean -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置目標Bean,該目標Bean將由Bean後處理器自動生成代理 -->
<bean id="test1" class="lee.TransactionTestImpl">
<!-- 依賴注入目標Bean所必需的數據源Bean -->
<property name="ds" ref="dataSource"/>
</bean>
<!-- 配置目標Bean,該目標Bean將由Bean後處理器自動生成代理 -->
<bean id="test2" class="lee.TestImpl">
<!-- 依賴注入目標Bean所必需的數據源Bean -->
<property name="ds" ref="dataSource"/>
</bean>
<!-- 配置事務攔截器Bean -->
<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.
TransactionInterceptor">
<!-- 事務攔截器bean須要依賴注入一個事務管理器 -->
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<!-- 下面定義事務傳播屬性 -->
<props>
<prop key="insert*">PROPAGATION_REQUIRED</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<!-- 定義BeanNameAutoProxyCreator的Bean後處理器 -->
<bean class="org.springframework.aop.framework.autoproxy.
BeanNameAutoProxyCreator">
<!-- 指定對知足哪些bean name的bean自動生成業務代理 -->
<property name="beanNames">
<!-- 下面是全部須要自動建立事務代理的Bean -->
<list>
<value>test1</value>
<value>test2</value>
</list>
<!-- 此處可增長其餘須要自動建立事務代理的Bean -->
</property>
<!-- 下面定義BeanNameAutoProxyCreator所需的攔截器 -->
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value>
<!-- 此處可增長其餘新的Interceptor -->
</list>
</property>
</bean>
</beans>
若是配置文件中僅有兩個目標Bean,可能不能很清楚地看出這種自動建立代理配置方式的優點,但若是有更多目標Bean須要自動建立事務代理,則能夠很好地體會到這種配置方式的優點:配置文件只須要簡單地配置目標Bean,而後在BeanNameAutoProxyCreator配置中增長一行便可。
提示:使用BeanNameAutoProxyCreator能夠自動建立事務代理,使用DefaultAdvisor- AutoProxyCreator也可自動建立事務代理。關於後一個Bean後處理器的配置方式,請參看前面6.2.7節的內容。
posted @ 2009-07-19 10:18 jadmin 閱讀(5) 評論(0) 編輯
Spring的事務管理不需與任何特定的事務API耦合。對不一樣的持久層訪問技術,編程式事務提供一致的事務編程風格,經過模板化的操做一致性地管理事務。聲明式事務基於Spring AOP實現,卻並不須要程序開發者成爲AOP專家,亦可輕易使用Spring的聲明式事務管理。
Spring事務策略是經過PlatformTransactionManager接口體現的,該接口是Spring事務策略的核心。該接口的源代碼以下:
public interface PlatformTransactionManager
{
//平臺無關的得到事務的方法
TransactionStatus getTransaction(TransactionDefinition definition)
throws TransactionException;
//平臺無關的事務提交方法
void commit(TransactionStatus status) throws TransactionException;
//平臺無關的事務回滾方法
void rollback(TransactionStatus status) throws TransactionException;
}
PlatformTransactionManager是一個與任何事務策略分離的接口,隨着底層不一樣事務策略切換,應用必須採用不一樣的實現類。PlatformTransactionManager接口沒有與任何事務資源捆綁在一塊兒,它能夠適應於任何的事務策略,結合Spring的IoC容器,能夠向PlatformTransactionManager注入相關的平臺特性。
PlatformTransactionManager接口有許多不一樣的實現類,應用程序面向與平臺無關的接口編程,對不一樣平臺的底層支持,由PlatformTransactionManager接口的實現類完成。從而,應用程序無須與具體的事務API耦合。所以,使用PlatformTransactionManager接口,可將代碼從具體的事務API中解耦出來。
即便使用特定容器管理的JTA,代碼依然無須執行JNDI查找,無須與特定的JTA資源耦合在一塊兒。經過配置文件,JTA資源傳給PlatformTransactionManager的實現類。所以,程序的代碼可在JTA事務管理和非JTA事務管理之間輕鬆切換。
在PlatformTransactionManager接口內,包含一個getTransaction(TransactionDefinition definition)方法,該方法根據一個TransactionDefinition參數,返回一個TransactionStatus對象。TransactionStatus對象表示一個事務。TransactionStatus被關聯在當前執行的線程。
getTransaction(TransactionDefinition definition)返回的TransactionStatus對象,多是一個新的事務,也多是一個已經存在的事務對象。若是當前執行的線程已經處於事務管理下,返回當前線程的事務對象,不然,返回當前線程的調用堆棧已有的事務對象。
TransactionDefinition接口定義了一個事務規則,該接口必須指定以下幾個屬性值:
● 事務隔離,當前事務和其餘事務的隔離程度。例如,這個事務可否看到其餘事務未提交的數據等。
● 事務傳播,一般,在事務中執行的代碼都會在當前事務中運行。可是,若是一個事務上下文已經存在,有幾個選項可指定該事務性方法的執行行爲。例如,大多數狀況下,簡單地在現有的事務上下文中運行;或者掛起現有事務,建立一個新的事務。Spring提供EJB CMT(Contain Manager Transaction,容器管理事務)中全部的事務傳播選項。
● 事務超時,事務在超時前能運行多久。事務的最長持續時間。若是事務一直沒有被提交或回滾,將在超出該時間後,系統自動回滾事務。
● 只讀狀態,只讀事務不修改任何數據。在某些狀況下(例如使用Hibernate時),只讀事務是很是有用的優化。
TransactionStatus表明事務自己,它提供了簡單的控制事務執行和查詢事務狀態的方法。這些方法在全部的事務API中都是相同的。TransactionStatus接口的源代碼以下:
public interface TransactionStatus
{
//判斷事務是不是新建的事務
boolean isNewTransaction();
//設置事務回滾
void setRollbackOnly();
//查詢事務是否已有回滾標誌
boolean isRollbackOnly();
}
Spring的事務管理由PlatformTransactionManager的不一樣實現類完成。在Spring上下文中配置PlatformTransactionManager Bean時,必須針對不一樣環境提供不一樣的實現類。
下面提供不一樣的持久層訪問環境,及其對應的PlatformTransactionManager實現類的 配置。
JDBC數據源的局部事務策略:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 定義數據源Bean,使用C3P0數據源實現 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<!-- 指定鏈接數據庫的驅動 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<!-- 指定鏈接數據庫的URL -->
<property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
<!-- 指定鏈接數據庫的用戶名 -->
<property name="user" value="root"/>
<!-- 指定鏈接數據庫的密碼 -->
<property name="password" value="32147"/>
<!-- 指定鏈接數據庫鏈接池的最大鏈接數 -->
<property name="maxPoolSize" value="40"/>
<!-- 指定鏈接數據庫鏈接池的最小鏈接數 -->
<property name="minPoolSize" value="1"/>
<!-- 指定鏈接數據庫鏈接池的初始化鏈接數 -->
<property name="initialPoolSize" value="1"/>
<!-- 指定鏈接數據庫鏈接池的鏈接最大空閒時間 -->
<property name="maxIdleTime" value="20"/>
</bean>
<!-- 配置JDBC數據源的局部事務管理器 -->
<!-- 使用DataSourceTransactionManager 類,該類實現PlatformTransactionManager接口 -->
<!-- 針對採用數據源鏈接的特定實現 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.
DataSourceTransactionManager">
<!-- DataSourceTransactionManager bean須要依賴注入一個DataSource
bean的引用 -->
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
對於容器管理JTA數據源,全局事務策略的配置文件以下:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置JNDI數據源Bean -->
<bean id="dataSource" class="org.springframework.jndi.
JndiObjectFactoryBean">
<!-- 容器管理數據源的JNDI -->
<property name="jndiName" value="jdbc/jpetstore"/>
</bean>
<!-- 使用JtaTransactionManager類,該類實現PlatformTransactionManager接
口 -->
<!-- 針對採用全局事務管理的特定實現 -->
<!-- JtaTransactionManager不須要知道數據源,或任何其餘特定資源 -->
<!-- 由於它使用容器的全局事務管理 -->
<bean id="transactionManager"
class="org.springframework.transaction.jta.
JtaTransactionManager" />
</beans>
對於採用Hibernate持久層訪問策略時,局部事務策略的配置文件以下:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 定義數據源Bean,使用C3P0數據源實現 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.
ComboPooledDataSource" destroy-method="close">
<!-- 指定鏈接數據庫的驅動 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<!-- 指定鏈接數據庫的URL -->
<property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
<!-- 指定鏈接數據庫的用戶名 -->
<property name="user" value="root"/>
<!-- 指定鏈接數據庫的密碼 -->
<property name="password" value="32147"/>
<!-- 指定鏈接數據庫鏈接池的最大鏈接數 -->
<property name="maxPoolSize" value="40"/>
<!-- 指定鏈接數據庫鏈接池的最小鏈接數 -->
<property name="minPoolSize" value="1"/>
<!-- 指定鏈接數據庫鏈接池的初始化鏈接數 -->
<property name="initialPoolSize" value="1"/>
<!-- 指定鏈接數據庫鏈接池的鏈接最大空閒時間 -->
<property name="maxIdleTime" value="20"/>
</bean>
<!-- 定義Hibernate的SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.
LocalSessionFactoryBean">
<!-- 依賴注入SessionFactory所需的數據源,正是上文定義的dataSource -->
<property name="dataSource" ref="dataSource"/>
<!-- mappingResources屬性用來列出所有映射文件 -->
<property name="mappingResources">
<list>
<!-- 如下用來列出全部的PO映射文件 -->
<value>lee/MyTest.hbm.xml</value>
</list>
</property>
<!-- 定義Hibernate的SessionFactory的屬性 -->
<property name="hibernateProperties">
<props>
<!-- 指定Hibernate的鏈接方言 -->
<prop key="hibernate.dialect">org.hibernate.dialect.
MySQLDialect</prop>
<!-- 是否根據Hibernate映射建立數據表時,選擇create、update、
create-drop -->
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 配置Hibernate的局部事務管理器 -->
<!-- 使用HibernateTransactionManager類,該類是PlatformTransactionManager
接口,針對採用Hibernate持久化鏈接的特定實現 -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.
HibernateTransactionManager">
<!-- HibernateTransactionManager Bean須要依賴注入一個
SessionFactorybean的引用 -->
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
</beans>
對於採用Hibernate持久層訪問策略時,全局事務策略的配置文件以下:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置JNDI數據源Bean -->
<bean id="dataSource" class="org.springframework.jndi.
JndiObjectFactoryBean">
<!-- 容器管理數據源的JNDI -->
<property name="jndiName" value="jdbc/jpetstore"/>
</bean>
<!--定義Hibernate的SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.
LocalSessionFactoryBean">
<!-- 依賴注入SessionFactory所需的數據源,正是上文定義的dataSource Bean -->
<property name="dataSource" ref="dataSource"/>
<!-- mappingResources屬性用來列出所有映射文件 -->
<property name="mappingResources">
<list>
<!-- 如下用來列出全部的PO映射文件 -->
<value>lee/MyTest.hbm.xml</value>
</list>
</property>
<!-- 定義Hibernate的SessionFactory的屬性 -->
<property name="hibernateProperties">
<props>
<!-- 指定Hibernate的鏈接方言 -->
<prop key="hibernate.dialect">org.hibernate.dialect.
MySQLDialect</prop>
<!-- 是否根據Hiberante映射建立數據表時,選擇create、update、
create-drop -->
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 使用JtaTransactionManager類,該類是PlatformTransactionManager接口,
針對採用數據源鏈接的特定實現 -->
<!-- JtaTransactionManager不須要知道數據源,或任何其餘特定資源,
由於使用容器的全局事務管理 -->
<bean id="transactionManager"
class="org.springframework.transaction.jta.
JtaTransactionManager" />
</beans>
不論採用哪一種持久層訪問技術,只要使用JTA數據源,Spring事務管理器的配置都是同樣的,由於它們都採用的是全局事務管理。
能夠看到,僅僅經過配置文件的修改,就能夠在不一樣的事務管理策略間切換,即便從局部事務到全局事務的切換。
提示:Spring所支持的事務策略很是靈活,Spring的事務策略容許應用程序在不一樣事務策略之間自由切換,即便須要在局部事務策略和全局事務策略之間切換,只須要修改配置文件,而應用程序的代碼無須任何改變。這種靈活的設計,又未嘗不是由於面向接口編程帶來的優點,可見面向接口編程給應用程序更好的適應性。
posted @ 2009-07-19 10:18 jadmin 閱讀(2) 評論(0) 編輯
當目標Bean的實現類實現了接口後,Spring AOP能夠爲其建立JDK動態代理,而無須使用CGLIB建立的代理,這種代理稱爲代理接口。
建立AOP代理必須指定兩個屬性:目標Bean和處理。實際上,不少AOP框架都以攔截器做爲處理。由於Spring AOP與IoC容器的良好整合,所以配置代理Bean時,徹底能夠利用依賴注入來管理目標Bean和攔截器Bean。
下面的示例演示了基於AOP的權限認證,它是簡單的TestService接口,該接口模擬Service組件,該組件內包含兩個方法:
● 查看數據。
● 修改數據。
接口的源代碼以下:
//Service組件接口
public interface TestService
{
//查看數據
void view();
//修改數據
void modify();
}
該接口的實現類實現兩個方法。由於篇幅限制,本示例並未顯示出完整的查看數據和修改數據的持久層操做,僅僅在控制檯打印兩行信息。實際的項目實現中,兩個方法的實現則改爲對持久層組件的調用,這不會影響示例程序的效果。實現類的源代碼以下:
TestService接口的實現類
public class TestServiceImpl implements TestService
{
//實現接口必須實現的方法
public void view()
{
System.out.println("用戶查看數據");
}
//實現接口必須實現的方法
public void modify()
{
System.out.println("用戶修改數據");
}
}
示例程序採用Around 處理做爲攔截器,攔截器中使用依賴注入得到當前用戶名。實際Web應用中,用戶應該從session中讀取。這不會影響示例代碼的效果。攔截器源代碼以下:
public class AuthorityInterceptor implements MethodInterceptor
{
//當前用戶名
private String user;
//依賴注入所必需的setter方法
public void setUser(String user)
{
this.user = user;
}
public Object invoke(MethodInvocation invocation) throws Throwable
{
//獲取當前攔截的方法名
String methodName = invocation.getMethod().getName();
//下面執行權限檢查
//對既不是管理員,也不是註冊用戶的狀況
if (!user.equals("admin") && !user.equals("registedUser"))
{
System.out.println("您無權執行該方法");
return null;
}
//對僅僅是註冊用戶,調用修改數據的狀況
else if (user.equals("registedUser") && methodName.equals
("modify"))
{
System.out.println("您不是管理員,沒法修改數據");
return null;
}
//對管理員或註冊用戶,查看數據的狀況
else
{
return invocation.proceed();
}
}
}
TestAction類依賴TestService。因篇幅關係,此處不給出TestAction的接口的源代碼,TestActionImpl的源代碼以下:
public class TestActionImpl
{
//將TestService做爲成員變量,面向接口編程
private TestService ts;
//依賴注入的setter方法
public void setTs(TestService ts)
{
this.ts = ts;
}
//修改數據
public void modify()
{
ts.modify();
}
//查看數據
public void view()
{
ts.view();
}
}
配置文件以下:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置目標Bean -->
<bean id="serviceTarget" class="lee.TestServiceImpl"/>
<!-- 配置攔截器,攔截器做爲處理使用 -->
<bean id="authorityInterceptor" class="lee.AuthorityInterceptor">
<property name="user" value="admin"/>
</bean>
<!-- 配置代理工廠Bean,負責生成AOP代理 -->
<bean id="service" class="org.springframework.aop.framework.
ProxyFactoryBean">
<!-- 指定AOP代理所實現的接口 -->
<property name="proxyInterfaces" value="lee.TestService"/>
<!-- 指定AOP代理所代理的目標Bean -->
<property name="target" ref="serviceTarget"/>
<!-- AOP代理所須要的攔截器列表 -->
<property name="interceptorNames">
<list>
<value>authorityInterceptor</value>
</list>
</property>
</bean>
<!-- 配置Action Bean,該Action依賴TestService Bean -->
<bean id="testAction" class="lee.TestActionImpl">
<!-- 此處注入的是依賴代理Bean -->
<property name="ts" ref="service"/>
</bean>
</beans>
主程序請求testAction Bean,而後調用該Bean的兩個方法,主程序以下:
public class BeanTest
{
public static void main(String[] args)throws Exception
{
//建立Spring容器實例
ApplicationContext ctx = new FileSystemXmlApplicationContext
("bean.xml");
//得到TestAction bean
TestAction ta = (TestAction)ctx.getBean("testAction");
//調用bean的兩個測試方法
ta.view();
ta.modify();
}
}
程序執行結果以下:
[java] 用戶查看數據
[java] 用戶修改數據
代理彷佛沒有發揮任何做用。由於配置文件中的當前用戶是admin,admin用戶具有訪問和修改數據的權限,所以代理並未阻止訪問。將配置文件中的admin修改爲registed- User,再次執行程序,獲得以下結果:
[java] 用戶查看數據
[java] 您不是管理員,沒法修改數據
代理阻止了registedUser修改數據,查看數據能夠執行。將registedUser修改爲其餘用戶,執行程序,看到以下結果:
[java] 您無權執行該方法
[java] 您無權執行該方法
代理阻止用戶對兩個方法的執行。基於AOP的權限檢查,能夠下降程序的代碼量,由於無須每次調用方法以前,手動編寫權限檢查代碼;同時,權限檢查與業務邏輯分離,提升了程序的解耦。
示例中的目標Bean被暴露在容器中,能夠被客戶端代碼直接訪問。爲了不客戶端代碼直接訪問目標Bean,能夠將目標Bean定義成代理工廠的嵌套Bean,修改後的配置文件以下:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置攔截器,攔截器做爲處理使用 -->
<bean id="authorityInterceptor" class="lee.AuthorityInterceptor">
<property name="user" value="admin"/>
</bean>
<!-- 配置代理工廠Bean,該工廠Bean將負責建立目標Bean的代理 -->
<bean id="service" class="org.springframework.aop.framework.
ProxyFactoryBean">
<!-- 指定AOP代理所實現的接口 -->
<property name="proxyInterfaces" value="lee.TestService"/>
<property name="target">
<!-- 以嵌套Bean的形式定義目標Bean,避免客戶端直接訪問目標Bean -->
<bean class="lee.TestServiceImpl"/>
</property>
<!-- AOP代理所須要的攔截器列表 -->
<property name="interceptorNames">
<list>
<value>authorityInterceptor</value>
</list>
</property>
</bean>
<!-- 配置Action Bean,該Action依賴TestService Bean -->
<bean id="testAction" class="lee.TestActionImpl">
<!-- 此處注入的是依賴代理Bean -->
<property name="ts" ref="service"/>
</bean>
</beans>
由上面介紹的內容可見,Spring的AOP是對JDK動態代理模式的深化。經過Spring AOP組件,容許經過配置文件管理目標Bean和AOP所需的處理。
下面將繼續介紹如何爲沒有實現接口的目標Bean建立CGLIB代理。
若是目標類沒有實現接口,則沒法建立JDK動態代理,只能建立CGLIB代理。若是須要沒有實現接口的Bean實例生成代理,配置文件中應該修改以下兩項:
● 去掉<property name="proxyInterfaces"/>聲明。由於再也不代理接口,所以,此處的配置沒有意義。
● 增長<property name="proxyTargetClass">子元素,並設其值爲true,經過該元素強制使用CGLIB代理,而不是JDK動態代理。
注意:最好面向接口編程,不要面向類編程。同時,即便在實現接口的狀況下,也可強制使用CGLIB代理。
CGLIB代理在運行期間產生目標對象的子類,該子類經過裝飾器設計模式加入到Advice中。由於CGLIB代理是目標對象的子類,則必須考慮保證以下兩點:
● 目標類不能聲明成final,由於final類不能被繼承,沒法生成代理。
● 目標方法也不能聲明成final,final方法不能被重寫,沒法獲得處理。
固然,爲了須要使用CGLIB代理,應用中應添加CGLIB二進制Jar文件。
這是一種自動建立事務代理的方式,一旦在容器中配置了BeanNameAutoProxyCreator實例,該實例將會對指定名字的Bean實例自動建立代理。實際上,BeanNameAutoProxyCreator是一個Bean後處理器,理論上它會對容器中全部的Bean進行處理,實際上它只對指定名字的Bean實例建立代理。
BeanNameAutoProxyCreator根據名字自動生成事務代理,名字匹配支持通配符。
與ProxyFactoryBean同樣,BeanNameAutoProxyCreator須要一個interceptorNames屬性,該屬性名雖然是「攔截器」,但並不須要指定攔截器列表,它能夠是Advisor或任何處理類型。
下面是使用BeanNameAutoProxyCreator的配置片斷:
<!-- 定義事務攔截器bean -->
<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.
TransactionInterceptor">
<!-- 事務攔截器bean須要依賴注入一個事務管理器 -->
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<!-- 下面定義事務傳播屬性 -->
<props>
<prop key="insert*">PROPAGATION_REQUIRED </prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<!-- 定義BeanNameAutoProxyCreator Bean,它是一個Bean後處理器,
負責爲容器中特定的Bean建立AOP代理 -->
<bean class="org.springframework.aop.framework.autoproxy.
BeanNameAutoProxyCreator">
<!-- 指定對知足哪些bean name的bean自動生成業務代理 -->
<property name="beanNames">
<list>
<!-- 下面是全部須要自動建立事務代理的Bean -->
<value>core-services-applicationControllerSevice</value>
<value>core-services-deviceService</value>
<value>core-services-authenticationService</value>
<value>core-services-packagingMessageHandler</value>
<value>core-services-sendEmail</value>
<value>core-services-userService</value>
<!-- 此處可增長其餘須要自動建立事務代理的Bean -->
</list>
</property>
<!-- 下面定義BeanNameAutoProxyCreator所需的攔截器 -->
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value>
<!-- 此處可增長其餘新的Interceptor -->
</list>
</property>
</bean>
上面的片斷是使用BeanNameAutoProxyCreator自動建立事務代理的片斷。Transaction- Interceptor用來定義事務攔截器,定義事務攔截器時傳入事務管理器Bean,也指定事務傳播屬性。
經過BeanNameAutoProxyCreator定義Bean後處理器,定義該Bean後處理器時,經過beanNames屬性指定有哪些目標Bean生成事務代理;還須要指定「攔截器鏈」,該攔截器鏈能夠由任何處理組成。
定義目標Bean還可以使用通配符,使用通配符的配置片斷以下所示:
<!-- 定義BeanNameAutoProxyCreator Bean,它是一個Bean後處理器,
負責爲容器中特定的Bean建立AOP代理 -->
<bean class="org.springframework.aop.framework.autoproxy.
BeanNameAutoProxyCreator">
<!-- 指定對知足哪些bean name的bean自動生成業務代理 -->
<property name="beanNames">
<!-- 此處使用通配符肯定目標bean -->
<value>*DAO,*Service,*Manager</value>
</property>
<!-- 下面定義BeanNameAutoProxyCreator所需的事務攔截器 -->
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value>
<!-- 此處可增長其餘新的Interceptor -->
</list>
</property>
</bean>
上面的配置片斷中,全部名字以DAO、Service、Manager結尾的bean,將由該「bean後處理器」爲其建立事務代理。目標bean再也不存在,取而代之的是目標bean的事務代理。經過這種方式,不只能夠極大地下降配置文件的繁瑣,並且能夠避免客戶端代碼直接調用目標bean。
注意:雖然上面的配置片斷是爲目標對象自動生成事務代理。但這不是惟一的,若是有須要,能夠爲目標對象生成任何的代理。BeanNameAutoProxyCreator爲目標對象生成怎樣的代理,取決於傳入怎樣的處理Bean,若是傳入事務攔截器,則生成事務代理Bean;不然將生成其餘代理,在後面的實例部分,讀者將能夠看到大量使用BeanNameAutoProxy- Creator建立的權限檢查代理。
Spring還提供了另外一個Bean後處理器,它也可爲容器中的Bean自動建立代理。相比之下,DefaultAdvisorAutoProxyCreator是更通用、更強大的自動代理生成器。它將自動應用於當前容器中的advisor,不須要在DefaultAdvisorAutoProxyCreator定義中指定目標Bean的名字字符串。
這種定義方式有助於配置的一致性,避免在自動代理建立器中重複配置目標Bean 名。
使用該機制包括:
● 配置DefaultAdvisorAutoProxyCreator bean定義。
● 配置任何數目的Advisor,必須是Advisor,不只僅是攔截器或其餘處理。由於,必須使用切入點檢查處理是否符合候選Bean定義。
DefaultAdvisorAutoProxyCreator計算Advisor包含的切入點,檢查處理是否應該被應用到業務對象,這意味着任何數目的Advisor均可自動應用到業務對象。若是Advisor中沒有切入點符合業務對象的方法,這個對象就不會被代理。若是增長了新的業務對象,只要它們符合切入點定義,DefaultAdvisorAutoProxyCreator將自動爲其生成代理,無須額外 配置。
當有大量的業務對象須要採用相同處理,DefaultAdvisorAutoProxyCreator是很是有用的。一旦定義恰當,直接增長業務對象,而不須要額外的代理配置,系統自動爲其增長 代理。
<beans>
<!-- 定義Hibernate局部事務管理器,可切換到JTA全局事務管理器 -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.
HibernateTransactionManager">
<!-- 定義事務管理器時,依賴注入SessionFactory -->
<property name="sessionFactory" ref bean="sessionFactory"/>
</bean>
<!-- 定義事務攔截器 -->
<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.
TransactionInterceptor">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributeSource">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
</bean>
<!-- 定義DefaultAdvisorAutoProxyCreator Bean,這是一個Bean後處理器 -->
<bean class="org.springframework.aop.framework.autoproxy.
DefaultAdvisorAutoProxyCreator"/>
<!-- 定義事務Advisor -->
<bean class="org.springframework.transaction.interceptor.
TransactionAttributeSourceAdvisor">
<property name="transactionInterceptor" ref=
"transactionInterceptor"/>
</bean>
<!-- 定義額外的Advisor>
<bean id="customAdvisor" class="lee.MyAdvisor"/>
</beans>
DefaultAdvisorAutoProxyCreator支持過濾和排序。若是須要排序,可以讓Advisor實現org.springframework.core.Ordered接口來肯定順序。TransactionAttributeSourceAdvisor已經實現Ordered接口,所以可配置其順序,默認是不排序。
採用這樣的方式,同樣能夠避免客戶端代碼直接訪問目標Bean,並且配置更加簡潔。只要目標Bean符合切入點檢查,Bean後處理器自動爲目標Bean建立代理,無須依次指定目標Bean名。
注意:Spring也支持以編程方式建立AOP代理,但這種方式將AOP代理所須要的目標對象和處理Bean等對象的耦合下降到代碼層次,所以不推薦使用。若是讀者須要深刻了解如何經過編程方式建立AOP代理,請參閱筆者所著的《Spring2.0寶典》。
posted @ 2009-07-19 10:16 jadmin 閱讀(1) 評論(0) 編輯
AOP(Aspect Orient Programming),也就是面向切面編程,做爲面向對象編程的一種補充。問世的時間並不太長,甚至在國內的翻譯還不太統一(有些書翻譯成面向方面編程),但它確實極好地補充了面向對象編程的方式。面向對象編程將程序分解成各個層次的對象,而面向切面編程將程序運行過程分解成各個切面。
能夠這樣理解,面向對象編程是從靜態角度考慮程序結構,面向切面編程是從動態角度考慮程序運行過程。
Spring AOP是Spring框架的一個重要組件,極好地補充了Spring IoC容器的功能。Spring AOP將Spring IoC容器與AOP組件緊密結合,豐富了IoC容器的功能。固然,即便不使用AOP組件,依然能夠使用Spring的IoC容器。
AOP從程序運行角度考慮程序的流程,提取業務處理過程的切面。AOP面向的是程序運行中各個步驟,但願以更好的方式來組合業務處理的各個步驟。
AOP框架並不與特定的代碼耦合,AOP框架能處理程序執行中的特定點,而不是某個具體的程序。AOP框架具備以下兩個特徵:
● 各步驟之間的良好隔離性。
● 源代碼無關性。
下面是關於面向切面編程的一些術語:
● 切面,業務流程運行的某個特定步驟,就是運行過程的關注點,關注點可能橫切多個對象。
● 鏈接點,程序執行過程當中明確的點,如方法的調用或異常的拋出。Spring AOP中,鏈接點老是方法的調用,Spring並無顯式地使用鏈接點。
● 處理(Advice),AOP框架在特定的鏈接點執行的動做。處理有around、before和throws等類型。大部分框架都以攔截器做爲處理模型。
● 切入點,系列鏈接點的集合,它肯定處理觸發的時機。AOP框架容許開發者本身定義切入點,如使用正則表達式。
● 引入,添加方法或字段到被處理的類。Spring容許引入新的接口到任何被處理的對象。例如,能夠使用一個引入,使任何對象實現IsModified接口,以此來簡化緩存。
● 目標對象,包含鏈接點的對象。也稱爲被處理對象或被代理對象。
● AOP代理,AOP框架建立的對象,包含處理。簡單地說,代理就是對目標對象的增強。Spring中的AOP代理能夠是JDK動態代理,也能夠是CGLIB代理。前者爲實現接口的目標對象的代理,後者爲不實現接口的目標對象的代理。
注意:面向切面編程是比較前沿的知識,而國內大部分翻譯人士翻譯計算機文獻時,老是一邊開着各類詞典和翻譯軟件,一邊逐詞去看文獻,不是先從整體上把握知識的架構。所以,不免致使一些術語的翻譯詞不達意,例如,Socket被翻譯成「套接字」等。在面向切面編程的各術語翻譯上,也存在較大的差別。對於Advice一詞,有翻譯爲「通知」的,有翻譯爲「建議」的,如此種種,不一而足。實際上,Advice指AOP框架在特定切面所作的事情,故而筆者翻譯爲「處理」,但願能夠表達Advice的真正含義。
所謂AOP代理,就是AOP框架動態建立的對象,這個對象一般能夠做爲目標對象的替代品,而AOP代理提供比目標對象更增強大的功能。真實的情形是,當應用調用AOP代理的方法時,AOP代理會在本身的方法中回調目標對象的方法,從而完成應用的調用。
關於AOP代理的典型例子就是Spring中的事務代理Bean。一般,目標Bean的方法不是事務性的,而AOP代理包含目標Bean的所有方法,並且這些方法通過增強變成了事務性方法。簡單地說,目標對象是藍本,AOP代理是目標對象的增強,在目標對象的基礎上,增長屬性和方法,提供更強大的功能。
目標對象包含一系列切入點。切入點能夠觸發處理鏈接點集合。用戶能夠本身定義切入點,如使用正則表達式。AOP代理包裝目標對象,在切入點處加入處理。在切入點加入的處理,使得目標對象的方法功能更強。
Spring默認使用JDK動態代理實現AOP代理,主要用於代理接口。也能夠使用CGLIB代理。實現類的代理,而不是接口。若是業務對象沒有實現接口,默認使用CGLIB代理。但面向接口編程是良好的習慣,儘可能不要面向具體類編程。所以,業務對象一般應實現一個或多個接口。
下面是一個簡單動態代理模式的示例,首先有一個Dog的接口,接口以下:
public interface Dog
{
//info方法聲明
public void info();
//run方法聲明
public void run();
}
而後,給出該接口的實現類,實現類必須實現兩個方法,源代碼以下:
public class DogImpl implements Dog
{
//info方法實現,僅僅打印一個字符串
public void info()
{
System.out.println("我是一隻獵狗");
}
//run方法實現,僅僅打印一個字符串
public void run()
{
System.out.println("我奔跑迅速");
}
}
上面的代碼沒有絲毫獨特之處,是典型的面向接口編程的模型,爲了有更好的解耦,採用工廠來建立Dog實例。工廠源代碼以下:
public class DogFactory
{
//工廠自己是單態模式,所以,將DogFactory做爲靜態成員變量保存
private static DogFactory df;
//將Dog實例緩存
private Dog gundog;
//默認的構造器,單態模式須要的構造器是private
private DogFactory()
{
}
//單態模式所需的靜態方法,該方法是建立本類實例的惟一方法點
public static DogFactory instance()
{
if (df == null)
{
df = new DogFactory();
}
return df;
}
//得到Dog實例
public Dog getDog(String dogName)
{
//根據字符串參數決定返回的實例
if (dogName.equals("gundog"))
{
//返回Dog實例以前,先判斷緩存的Dog是否存在,若是不存在才建立,
不然直接返回緩存的Dog實例
if (gundog == null )
{
gundog = new DogImpl();
}
return gundog;
}
return null;
}
}
下面是一個通用的處理類,該處理類沒有與任何特定的類耦合,它能夠處理全部的目標對象。從JDK 1.3起,Java的import java.lang.reflect下增長InvocationHandler接口,該接口是全部處理類的根接口。
該類處理類的源代碼以下:
public class ProxyHandler implements InvocationHandler
{
//需被代理的目標對象
private Object target;
//執行代理的目標方法時,該invoke方法會被自動調用
public Object invoke(Object proxy, Method method, Object[] args)throws
Exception
{
Object result = null;
if (method.getName().equals("info"))
{
System.out.println("======開始事務...");
result =method.invoke(target, args);
System.out.println("======提交事務...");
}
else
{
result =method.invoke(target, args);
}
return result;
}
//經過該方法,設置目標對象
public void setTarget(Object o)
{
this.target = o;
}
}
該處理類實現InvocationHandler接口,實現該接口必須實現invoke(Object proxy, Method method, Object[] args)方法,程序調用代理的目標方法時,自動變成調用invoke方法。
該處理類並未與任何接口或類耦合,它徹底是通用的,它的目標實例是Object類型,能夠是任何的類型。
在invoke方法內,對目標對象的info方法進行簡單增強,在開始執行目標對象的方法以前,先打印開始事務,執行目標對象的方法以後,打印提交事務。
經過method對象的invoke方法,能夠完成目標對象的方法調用,執行代碼以下:
result =method.invoke(target, args);
下面是代理工廠:
public class MyProxyFactory
{
/**
* 實例Service對象
* @param serviceName String
* @return Object
*/
public static Object getProxy(Object object)
{
//代理的處理類
ProxyHandler handler = new ProxyHandler();
//把該dog實例託付給代理操做
handler.setTarget(object);
//第一個參數是用來建立動態代理的ClassLoader對象,只要該對象能訪問Dog接口
便可
//第二個參數是接口數組,正是代理該接口數組
//第三個參數是代理包含的處理實例
return Proxy.newProxyInstance(DogImpl.class.getClassLoader(),
object.getClass().getInterfaces(),handler);
}
}
代理工廠裏有一行代碼:
Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),handler);
Proxy.newProxyInstance()方法根據接口數組動態建立代理類實例,接口數組經過object.getClass().getInterfaces()方法得到,建立的代理類是JVM在內存中動態建立的,該類實現傳入接口數組的所有接口。
所以,Dynamic Proxy要求被代理的必須是接口的實現類,不然沒法爲其構造相應的動態類。所以,Spring對接口實現類採用Dynamic Proxy實現AOP,而對沒有實現任何接口的類,則經過CGLIB實現AOP代理。
下面是主程序:
public class TestDog
{
public static void main(String[] args)
{
Dog dog = null;
//建立Dog實例,該實例將做爲被代理對象
Dog targetObject = DogFactory.instance().getDog("gundog");
//以目標對象建立代理
Object proxy = MyProxyFactory.getProxy(targetObject);
if (proxy instanceof Dog)
{
dog = (Dog)proxy;
}
//測試代理的方法
dog.info();
dog.run();
}
}
代理實例會實現目標對象實現的所有接口。所以,代理實例也實現了Dog接口,程序運行結果以下:
[java] ======開始事務...
[java] 我是一隻獵狗
[java] ======提交事務...
[java] 我奔跑迅速
代理實例增強了目標對象的方法——僅僅打印了兩行字符串。固然,此種增強沒有實際意義。試想一下,若程序中打印字符串的地方,換成真實的事務開始和事務提交,則代理實例的方法爲目標對象的方法增長了事務性。
經過前面的介紹,AOP代理就是由AOP框架動態生成的一個對象,該對象可做爲目標對象使用,AOP代理包含了目標對象的所有方法。但AOP代理中的方法與目標對象的方法存在差別:AOP方法在特定切面插入處理,在處理之間回調目標對象的方法。
AOP代理所包含的方法與目標對象所包含的方法的示意圖,如圖6.1所示。
Spring中AOP代理由Spring的IoC容器負責生成和管理,其依賴關係也由IoC容器負責管理。所以,AOP代理可以引用容器中的其餘Bean實例,這種引用由IoC容器的依賴注入提供。
Spring的AOP代理大都由ProxyFactoryBean工廠類產生,如圖6.1所示,產生一個AOP代理至少有兩個部分:目標對象和AOP框架所加入的處理。所以,配置ProxyFactoryBean時須要肯定以下兩個屬性:
● 代理的目標對象。
● 處理(Advice)。
注意:關於Advice的更多知識,此處因爲篇幅緣由,沒法深刻討論。實際上,Spring也提供了不少種Advice的實現,如須要更深刻了解Spring AOP,請讀者參考筆者所著的《Spring2.0寶典》。
全部代理工廠類的父類是org.springframework.aop.framework.ProxyConfig。所以,該類的屬性是全部代理工廠的共同屬性,這些屬性也是很是關鍵的,包括:
● proxyTargetClass,肯定是否代理目標類,若是須要代理目標是類,該屬性設爲true,此時須要使用CGLIB生成代理;若是代理目標是接口,該屬性設爲false,默認是false。
● optimize,肯定是否使用強優化來建立代理。該屬性僅對CGLIB代理有效;對JDK 動態代理無效。
● frozen,肯定是否禁止改變處理,默認是false。
● exposeProxy,代理是否能夠經過ThreadLocal訪問,若是exposeProxy屬性爲true,則可經過AopContext.currentProxy()方法得到代理。
● aopProxyFactory,所使用的AopProxyFactory具體實現。該參數用來指定使用動態代理、CGLIB或其餘代理策略。默認選擇動態代理或CGLIB。通常不須要指定該屬性,除非須要使用新的代理類型,才指定該屬性。
配置ProxyFactoryBean工廠bean時,還須要指定它的特定屬性,ProxyFactoryBean的特定屬性以下所示:
● proxyInterfaces,接口名的字符串數組。若是沒有肯定該參數,默認使用CGLIB代理。
● interceptorNames,處理名的字符串數組。此處的次序很重要,排在前面的處理,優先被調用。此處的處理名,只能是當前工廠中處理的名稱,而不能使用bean引用。處理名字支持使用通配符(*)。
● singleton,工廠是否返回單態代理。默認是true,不管 getObject()被調用多少次,將返回相同的代理實例。若是須要使用有狀態的處理——例如,有狀態的mixin,可改變默認設置,prototype處理。
posted @ 2009-07-19 10:15 jadmin 閱讀(1) 評論(0) 編輯
Spring 框架提供了很好的擴展性,除了能夠與各類第三方框架良好整合外,其IoC容器也容許開發者進行擴展。這種擴展並非經過實現BeanFactory或ApplicationContext的子類,而是經過兩個後處理器對IoC容器進行擴展。Spring提供了兩種經常使用的後處理器:
● Bean後處理器,這種後處理器會對容器中特定的Bean進行定製,例如功能的 增強。
● 容器後處理器,這種後處理器對IoC容器進行特定的後處理。
下面將介紹這兩種經常使用的後處理器以及兩種後處理器相關知識。
Bean後處理器是一種特殊的Bean,這種特殊的Bean並不對外提供服務,它無須id屬性,但它負責對容器中的其餘Bean執行後處理,例如爲容器中的目標Bean生成代理。這種Bean可稱爲Bean後處理器,它在Bean實例建立成功後,對其進行進一步的增強 處理。
Bean後處理器必須實現BeanPostProcessor接口。
BeanPostProcessor接口包含兩個方法:
● Object postProcessBeforeInitialization(Object bean, String name)throws BeansExce- ption,該方法的第一個參數是系統即將初始化的Bean實例,第二個參數是Bean實例的名字。
● Object postProcessAfterInitialization(Object bean, String name)throws BeansExce- ption,該方法的第一個參數是系統剛完成初始化的Bean實例,第二個參數是Bean實例的名字。
實現該接口的Bean必須實現這兩個方法,這兩個方法會對容器的Bean進行後處理。兩個方法會在目標Bean初始化以前和初始化以後分別調用。這兩個方法用於對系統完成的默認初始化進行增強。
注意:Bean後處理器是對IoC容器一種極好的擴展,Bean後處理器能夠對容器中的Bean進行後處理,這種後處理徹底由開發者決定。
下面將定義一個簡單的Bean後處理器,該Bean後處理器將對容器中其餘Bean進行後處理。Bean後處理器的代碼以下:
//自定義Bean後處理器,負責後處理容器中全部的Bean
public class MyBeanPostProcessor implements BeanPostProcessor
{
//在初始化bean以前,調用該方法
public Object postProcessBeforeInitialization(Object bean , String
beanName)throws BeansException
{
//僅僅打印一行字符串
System.out.println("系統正在準備對" + beanName + "進行初始化...");
return bean;
}
//在初始化bean以後,調用該方法
public Object postProcessAfterInitialization(Object bean , String
beanName)throws BeansException
{
System.out.println("系統已經完成對" + beanName + "的初始化");
//若是系統剛完成初始化的bean是Chinese
if (bean instanceof Chinese)
{
//爲Chinese實例設置name屬性
Chinese c = (Chinese)bean;
c.setName("wawa");
}
return bean;
}
}
下面是Chinese的源代碼,該類實現了InitializingBean接口,還額外提供了一個初始化方法,這兩個方法都由Spring容器控制回調。
public class Chinese implements Person,InitializingBean
{
private Axe axe;
private String name;
public Chinese()
{
System.out.println("Spring實例化主調bean:Chinese實例...");
}
public void setAxe(Axe axe)
{
System.out.println("Spring執行依賴關係注入...");
this.axe = axe;
}
public void setName(String name)
{
this.name = name;
}
public void useAxe()
{
System.out.println(name + axe.chop());
}
public void init()
{
System.out.println("正在執行初始化方法 init...");
}
public void afterPropertiesSet() throws Exception
{
System.out.println("正在執行初始化方法 afterPropertiesSet...");
}
}
配置文件以下:
<?xml version="1.0" encoding="gb2312"?>
<!-- 指定Spring 配置文件的dtd>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<!-- Spring配置文件的根元素 -->
<beans>
<!-- 配置bean後處理器,能夠沒有id屬性,此處id屬性爲了後面引用 -->
<bean id="beanPostProcessor" class="lee.MyBeanPostProcessor"/>
<bean id="steelAxe" class="lee.SteelAxe"/>
<bean id="chinese" class="lee.Chinese" init-method="init">
<property name="axe" ref="steelAxe"/>
</bean>
</beans>
本應用的chinese具備兩個初始化方法:
● init-method指定初始化方法。
● 實現InitializingBean接口,提供了afterPropertiesSet初始化方法。
MyBeanPostProcessor類實現了BeanPostProcessor接口,並實現了該接口的兩個方法,這兩個方法分別在初始化方法調用以前和以後獲得回調。
注意:上面的配置文件配置Bean後處理器時,依然爲Bean處理器指定了id屬性,指定id屬性是爲了方便程序經過該id屬性訪問Bean後處理器。大部分時候,程序無須手動訪問該Bean後處理器,所以無須爲其指定id屬性。
主程序以下:
public class BeanTest
{
public static void main(String[] args)throws Exception
{
//CLASSPATH路徑下的bean.xml文件建立Resource對象
ClassPathResource isr = new ClassPathResource("bean.xml");
//以Resource對象做爲參數,建立BeanFactory的實例
XmlBeanFactory factory = new XmlBeanFactory(isr);
//獲取Bean後處理器實例
MyBeanPostProcessor beanProcessor =
(MyBeanPostProcessor)factory.getBean("beanPostProcessor");
//註冊BeanPostProcessor實例
factory.addBeanPostProcessor(beanProcessor);
System.out.println("程序已經實例化BeanFactory...");
Person p = (Person)factory.getBean("chinese");
System.out.println("程序中已經完成了chinese bean的實例化...");
p.useAxe();
}
}
若是使用BeanFactory做爲Spring容器,必須手動註冊Bean後處理器,所以在程序中先獲取Bean後處理器實例,而後手動註冊——這就是在配置文件中指定Bean後處理器id屬性的緣由。經過BeanFactory的addBeanPostProcessor能夠註冊BeanPostProcessor實例。程序執行結果以下:
[java] 程序已經實例化BeanFactory...
[java] Spring實例化主調bean:Chinese實例...
[java] Spring實例化依賴bean:SteelAxe實例...
[java] 系統正在準備對steelAxe進行初始化...
[java] 系統已經完成對steelAxe的初始化
[java] Spring執行依賴關係注入...
[java] 系統正在準備對chinese進行初始化...
[java] 正在執行初始化方法 afterPropertiesSet...
[java] 正在執行初始化方法 init...
[java] 系統已經完成對chinese的初始化
[java] 程序中已經完成了chinese bean的實例化...
[java] wawa鋼斧砍柴真快
在配置文件中配置chinese實例時,並未指定name屬性值。但程序執行時,name屬性有了值,這就是Bean後處理器完成的,在Bean後處理器中判斷Bean是不是Chinese實例,而後設置它的name屬性。
容器中一旦註冊了Bean後處理器,Bean後處理器會自動啓動,在容器中每一個Bean建立時自動工做,完成加入Bean後處理器須要完成的工做。
實現BeanPostProcessor接口的Bean後處理器可對Bean進行任何操做,包括徹底忽略這個回調。BeanPostProcessor一般用來檢查標記接口或將Bean包裝成一個Proxy的事情。Spring的不少工具類,就是經過Bean後處理器完成的。
從主程序中看到,採用BeanFactory做爲Spring容器時,必須手動註冊BeanPost- Processor。而對於ApplicationContext,則無須手動註冊。ApplicationContext可自動檢測到容器中的Bean後處理器,自動註冊。Bean後處理器會在Bean實例建立時,自動啓動。即主程序採用以下代碼,效果徹底同樣:
public class BeanTest
{
public static void main(String[] args)throws Exception
{
ApplicationContext ctx = new ClassPathXmlApplicationContext
("bean.xml");
Person p = (Person)factory.getBean("chinese");
System.out.println("程序中已經完成了chinese bean的實例化...");
p.useAxe();
}
}
使用ApplicationContext做爲容器,無須手動註冊BeanPostProcessor。所以,若是須要使用Bean後處理器,Spring容器建議使用ApplicationContext,而不是BeanFactory。
上一節介紹了一個簡單的Bean後處理器,上面的Bean後處理器負責對容器中的Chinese Bean進行後處理,無論Chinese Bean如何初始化,老是將Chinese Bean的name屬性設置爲wawa。這種後處理看起來做用並非特別大。
實際上,Bean後處理器完成的工做更加實際,例如生成Proxy。Spring框架自己提供了大量的Bean後處理器,這些後處理器負責對容器中的Bean進行後處理。
下面是Spring提供的兩個經常使用的後處理器:
● BeanNameAutoProxyCreator,根據Bean實例的name屬性,建立Bean實例的代理。
● DefaultAdvisorAutoProxyCreator,根據提供的Advisor,對容器中全部的Bean實例建立代理。
上面提供的兩個Bean後處理器,都用於根據容器中配置的攔截器建立目標Bean代理,目標代理就在目標Bean的基礎上修改獲得。
注意:若是須要對容器中某一批Bean進行特定的處理,能夠考慮使用Bean後處理器。
除了上面提供的Bean後處理器外,Spring還提供了一種容器後處理器。Bean後處理器負責後處理容器生成的全部Bean,而容器後處理器則負責後處理容器自己。
容器後處理器必須實現BeanFactoryPostProcessor接口。實現該接口必須實現以下一個方法:
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
實現該方法的方法體就是對Spring容器進行的處理,這種處理能夠對Spring容器進行任意的擴展,固然也能夠對Spring容器不進行任何處理。
相似於BeanPostProcessor,ApplicationContext可自動檢測到容器中的容器後處理器,而且自動註冊容器後處理器。但若使用BeanFactory做爲Spring容器,則必須手動註冊後處理器。
下面定義了一個容器後處理器,這個容器後處理器實現BeanFactoryPostProcessor接口,但並未對Spring容器進行任何處理,只是打印出一行簡單的信息。該容器後處理器的代碼以下:
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor
{
//容器後處理器對容器進行的處理在該方法中實現
public void postProcessBeanFactory(ConfigurableListableBeanFactory
beanFactory)
throws BeansException
{
System.out.println("程序對Spring所作的BeanFactory的初始化沒有意
見...");
}
}
將該Bean做爲普通Bean部署在容器中,而後使用ApplicationContext做爲容器,容器會自動調用BeanFactoryPostProcessor處理Spring容器。程序執行效果以下:
[java] 程序對Spring所作的BeanFactory的初始化沒有意見...
實現BeanFactoryPostProcessor接口的Bean後處理器不只可對BeanFactory執行後處理,也能夠對ApplicationContext容器執行後處理。容器後處理器還可用來註冊額外的屬性編輯器。
注意:Spring沒有提供ApplicationContextPostProcessor。也就是說,對於Application- Context容器,同樣使用BeanFactoryPostProcessor做爲容器後處理器。
Spring已提供以下兩個經常使用的容器後處理器,包括:
● PropertyResourceConfigurer,屬性佔位符配置器。
● PropertyPlaceHolderConfigurer,另外一種屬性佔位符配置器。
下面將詳細介紹這兩種經常使用的容器後處理器。
Spring提供了PropertyPlaceholderConfigurer,它是一個容器後處理器,負責讀取Java屬性文件裏的屬性值,並將這些屬性值設置到Spring容器定義中。
經過使用PropertyPlaceholderConfigurer後處理器,能夠將Spring配置文件中的部分設置放在屬性文件中設置。這種配置方式固然有其優點:能夠將部分類似的配置(如數據庫的urls、用戶名和密碼)放在特定的屬性文件中,若是隻須要修改這部分配置,則無須修改Spring配置文件,修改屬性文件便可。
下面的配置文件配置了PropertyPlaceholderConfigurer後處理器,在配置數據源Bean時,使用了屬性文件中的屬性值。配置文件的代碼以下:
<?xml version="1.0" encoding="GBK"?>
<!-- beans是Spring配置文件的根元素,而且指定了Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置一個容器後處理器Bean -->
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.
PropertyPlaceholderConfigurer">
<!-- locations屬性指定屬性文件的位置 -->
<property name="locations">
<list>
<value>dbconn.properties</value>
<!-- 若是有多個屬性文件,依次在下面列出來 -->
</list>
</property>
</bean>
<!-- 定義數據源Bean,使用C3P0數據源實現 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.
ComboPooledDataSource" destroy-method="close">
<!-- 指定鏈接數據庫的驅動 -->
<property name="driverClass" value="${jdbc.driverClassName}"/>
<!-- 指定鏈接數據庫的URL -->
<property name="jdbcUrl" value="${jdbc.url}"/>
<!-- 指定鏈接數據庫的用戶名 -->
<property name="user" value="${jdbc.username}"/>
<!-- 指定鏈接數據庫的密碼 -->
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
在上面的配置文件中,配置driverClass和jdbcUrl等信息時,並未直接設置這些屬性的屬性值,而是設置了${jdbc.driverClassName}和${jdbc.url}屬性值。這代表Spring容器將從propertyConfigurer指定屬性文件中搜索這些key對應的value,併爲該Bean的屬性值設置這些value值。
如前所述,ApplicationContext會自動檢測部署在容器的容器後處理器,無須額外的註冊,容器自動註冊。所以,只需提供以下Java Properties文件:
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/j2ee
jdbc.username=root
jdbc.password=32147
經過這種方法,可從主XML配置文件中分離出部分配置信息。若是僅須要修改數據庫鏈接屬性,則無須修改主XML配置文件,只須要修改屬性文件便可。採用屬性佔位符的配置方式,能夠支持使用多個屬性文件。經過這種方式,可將配置文件分割成多個屬性文件,從而下降修改配置的風險。
注意:對於數據庫鏈接等信息集中的配置,能夠將其配置在Java屬性文件中,但不要過多地將Spring配置信息抽離到Java屬性文件中,不然可能會下降Spring配置文件的可讀性。
PropertyOverrideConfigurer是Spring提供的另外一個容器後處理器,這個後處理器的額做用與上面介紹的容器後處理器做用大體相同。但也存在些許差異:PropertyOverride- Configurer使用的屬性文件用於覆蓋XML配置文件中的定義。即PropertyOverride- Configurer容許XML配置文件中有默認的配置信息。
若是PropertyOverrideConfigurer的屬性文件有對應配置信息,XML文件中的配置信息被覆蓋;不然,直接使用XML文件中的配置信息。使用PropertyOverrideConfigurer的屬性文件,應是以下的格式:
beanName.property=value
beanName是屬性佔位符試圖覆蓋的Bean名,property是試圖覆蓋的屬性名。看以下配置文件:
<?xml version="1.0" encoding="GBK"?>
<!-- beans是Spring配置文件的根元素,而且指定了Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置一個屬性佔位符Bean。ApplictionContext能自動識別
PropertyPlaceholderConfigurer Bean -->
<bean id="propertyOverrider"
class="org.springframework.beans.factory.config.
PropertyOverrideConfigurer">
<property name="locations">
<list>
<value>dbconn.properties</value>
<!-- 若是有多個屬性文件,依次在下面列出來 -->
</list>
</property>
</bean>
<!-- 定義數據源Bean,使用C3P0數據源實現 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.
ComboPooledDataSource" destroy-method="close">
<!-- 指定鏈接數據庫的驅動 -->
<property name="driverClass" value="dd"/>
<!-- 指定鏈接數據庫的URL -->
<property name="jdbcUrl" value="xx"/>
<!-- 指定鏈接數據庫的用戶名 -->
<property name="user" value="dd"/>
<!-- 指定鏈接數據庫的密碼 -->
<property name="password" value="xx"/>
</bean>
</beans>
上面的配置文件中,指定數據源Bean的各類屬性值時,只是隨意指定了幾個屬性值,很明顯經過這幾個屬性值沒法鏈接到數據庫服務。
但由於Spring容器中部署了一個PropertyOverrideConfigurer的容器後處理器,並且Spring容器使用ApplicationContext做爲容器,它會自動檢測容器中的容器後處理器,無須額外的註冊,容器自動註冊該後處理器。
PropertyOverrideConfigurer後處理器讀取dbconn.properties文件中的屬性,用於覆蓋目標Bean的屬性。所以,若是屬性文件中有dataSource Bean屬性的設置,則配置文件中指定的屬性值將沒有任何做用。
dbconn.properties屬性文件以下:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://wonder:3306/j2ee
dataSource.username=root
dataSource.password=32147
注意屬性文件的格式必須是:
beanName.property=value
也就是說,dataSource必須是容器中真實存在的bean名,不然程序將出錯。
注意:程序沒法知道BeanFactory定義是否被覆蓋。僅僅經過察看XML配置文件,沒法知道配置文件的配置信息是否被覆蓋。若有多個PorpertyOverrideConfigurer對同一Bean屬性定義了覆蓋,最後一個覆蓋獲勝。
posted @ 2009-07-19 10:12 jadmin 閱讀(2) 評論(0) 編輯
前面介紹了Hibernate的一些相關知識點,距離Hibernate進入實際開發還有一段路要走。Hibernate做爲持久層解決方案,必須與其餘表現層技術組合在一塊兒纔可造成一個J2EE開發框架。常常看到網上一些朋友給出的Hibernate入門示例,竟然在JSP頁面中訪問Hibernate Configuratioin對象。甚至看到某些所謂的精通J2EE書籍,也竟然在JSP頁面中訪問Hibernate的Configuration對象——這種現狀很是讓人擔心,Hibernate並非萬金油,並非說項目中使用Hibernate就怎麼了不得了,而是經過使用Hibernate,可讓J2EE應用架構更科學,可讓開發者以更好的面向對象的方式進行項目開發。
反過來講,即便不使用Hibernate,而使用普通的JDBC持久化解決方案,也不該該在JSP(表現層)訪問到JDBC API(持久層API)。下面介紹如何讓Hibernate和Struts進行整合,整合Spring部分將在後面章節介紹。
工廠模式是指當應用程序中A組件須要B組件協助時,並非直接建立B組件的實例,而是經過B組件的工廠——該工廠能夠生成某一個類型組件的實例。在這種模式下,A組件無須與B組件以硬編碼方式耦合在一塊兒,而只須要與B組件的工廠耦合。
對於A組件而言,它只關心工廠生產的實例是否知足某種規範,即實現了某個接口(知足接口規範,便可供本身正常調用)。這種模式提供了對象之間清晰的角色劃分,下降了程序的耦合。
接口產生的所有實例一般實現相同接口,接口裏定義所有實例共同擁有的方法,這些方法在不一樣的實現類中實現方式不一樣。程序調用者無須關心方法的具體實現,從而下降了系統異構的代價。
下面是工廠模式的示例代碼:
//Person接口定義
public interface Person
{
/**
* @param name 對name打招呼
* @return 打招呼的字符串
*/
public String sayHello(String name);
/**
* @param name 對name告別
* @return 告別的字符串
*/
public String sayGoodBye(String name);
}
該接口定義Person的規範,該接口必須擁有兩個方法:能打招呼、能告別。規範要求實現該接口的類必須具備這兩個方法:
//American類實現Person接口
public class American implements Person
{
/**
* @param name 對name打招呼
* @return 打招呼的字符串
*/
public String sayHello(String name)
{
return name + ",Hello";
}
/**
* @param name 對name告別
* @return 告別的字符串
*/
public String sayGoodBye(String name)
{
return name + ",Good Bye";
}
}
下面是實現Person接口的另外一個實現類Chinese
public class Chinese implements Person
{
/**
* @param name 對name打招呼
* @return 打招呼的字符串
*/
public String sayHello(String name)
{
return name + ",您好";
}
/**
* @param name 對name告別
* @return 告別的字符串
*/
public String sayGoodBye(String name)
{
return name + ",下次再見";
}
}
而後看Person工廠的代碼:
public class PersonFactory
{
/**
* 得到Person實例的工廠方法
* @ param ethnic 調用該實例工廠方法傳入的參數
* @ return返回Person實例
*/
public Person getPerson(String ethnic)
{
//根據參數返回Person接口的實例
if (ethnic.equalsIgnoreCase("chin"))
{
return new Chinese();
}
else
{
return new American();
}
}
}
最簡單的工廠模式的框架基本如上所示。
主程序部分僅僅須要與工廠耦合,而無須與具體的實現類耦合在一塊兒。下面是主程序部分:
public class FactroyTest
{
public static void main(String[] args)
{
//建立PersonFactory的實例,得到工廠實例
PersonFactory pf = new PersonFactory();
//定義接口Person的實例,面向接口編程
Person p = null;
//使用工廠得到Person的實例
p = pf.getPerson("chin");
//下面調用Person接口的方法
System.out.println(p.sayHello("wawa"));
System.out.println(p.sayGoodBye("wawa"));
//使用工廠得到Person的另外一個實例
p = pf.getPerson("ame");
//再次調用Person接口的方法
System.out.println(p.sayHello("wawa"));
System.out.println(p.sayGoodBye("wawa"));
}
}
主程序從Person接口的具體類中解耦出來,並且程序調用者無須關心Person的實例化過程,角色劃分清晰。主程序僅僅與工廠服務定位結合在一塊兒,得到工廠的引用,程序將可得到全部工廠產生的實例。具體類的變化,重要接口不發生任何改變,調用者程序代碼部分幾乎無須發生任何改動。
第1章介紹了J2EE應用的架構,最上面的表現層,表現層與MVC框架的控制器交互,控制器負責調用業務邏輯組件的業務邏輯方法來處理用戶請求,而業務邏輯組件則依賴於DAO組件提供的數據庫原子操做,這種模式也被稱爲DAO模式。
由上面關於J2EE應用架構的介紹可見,控制器老是依賴於業務邏輯組件,而業務邏輯組件老是依賴於DAO組件。也就是說,控制器須要調用業務邏輯組件的方法,而業務邏輯組件須要調用DAO組件的方法。
DAO模式的分層很是清晰,持久層訪問被封裝在DAO層下,而決不會擴散到業務邏輯層,更不會在JSP頁面(表現層)中進行持久層訪問。
注意:即便在早期的Model 1(使用JSP + JavaBean建立應用的模式,沒有使用MVC設計模式)模式下,持久層訪問也被封裝在JavaBean中完成,而不是直接在JSP頁面中進行數據庫訪問。對於直接在JSP中訪問持久層API的作法,能夠說根本不瞭解J2EE開發。
那麼控制器採用怎樣的方式訪問業務邏輯組件呢?應該採用工廠模式,讓控制器與業務邏輯組件的實現類分離,僅與業務邏輯工廠耦合;一樣,業務邏輯組件也應該採用工廠模式訪問DAO模式,而不是直接與DAO實現類耦合。
後面的案例部分會介紹更實際的整合策略,此處僅僅介紹DAO模式下兩個工廠模式策略。
在J2EE應用開發中,可擴展性是一個隨時須要關注的問題。而DAO組件是常常須要增長的項目組件,若是每次須要增長一個DAO組件都須要修改代碼是至關讓人沮喪的事情。爲了不這種狀況,採用XML配置文件來管理全部的DAO組件,這種DAO組件配置文件的代碼以下:
<?xml version="1.0" encoding="GBK"?>
<daoContext>
<!-- 配置應用須要的sonDao組件 -->
<dao id="sonDao" class="org.yeeku.dao.impl.SonDaoImpl"/>
<!-- 配置應用須要的personDao組件 -->
<dao id="personDao" class="org.yeeku.dao.impl.PersonDaoImpl"/>
</daoContext>
查看上面的配置文件能夠看出,應用中有配置了兩個DAO組件,由於每一個DAO組件在J2EE應用中僅須要一個實例就足夠了,所以DAO工廠類提供了一個緩存池來緩存每一個DAO實例,並負責在應用啓動時建立全部的DAO。
下面是DAO工廠類的代碼:
public class DaoFactory
{
//用於緩存DAO實例的Map對象
private Map<String, Dao> daoMap = new HashMap<String , Dao>();
//將DAO工廠寫成單態模式
private static DaoFactory df;
//DAO工廠的構造器
private DaoFactory()throws Exception
{
//使用SAXReader來負責解析daoContext.xml配置文檔
Document doc = new SAXReader().read(new File(ConstantsUtil.realPath
+ "\\daoContext.xml"));
//獲取文檔的根文檔
Element root = doc.getRootElement();
//獲取daoContext根元素的全部子元素
List el = root.elements();
for (Iterator it = el.iterator();it.hasNext() ; )
{
//每一個子元素對應一個DAO組件
Element em = (Element)it.next();
String id = em.attributeValue("id");
//獲取實現類
String impl = em.attributeValue("class");
//經過反射,根據類名建立DAO組件的實例
Class implClazz = Class.forName(impl);
Dao d = (Dao)implClazz.newInstance();
//將建立的DAO組件放入緩存池中
daoMap.put(id, d);
}
}
//單態模式必須提供一個入口方法來建立DAO工廠的方法
public static DaoFactory instance()throws Exception
{
//若是DAO工廠還未建立
if (df == null)
{
df = new DaoFactory();
}
return df;
}
//下面的方法用於根據DAO組件ID獲取DAO組件
public Dao getDao(String id)
{
return daoMap.get(id);
}
}
經過上面的工廠類代碼能夠看出,DAO工廠負責初始化全部的DAO組件。系統每增長一個DAO組件,無須再修改任何代碼,僅僅須要在daoContext.xml配置文件中增長配置便可。
注意:這種整合策略很是優秀。可擴展性很好,若是應用須要增長一個DAO組件,只須要修改配置文件,並提供相應的DAO組件實現便可。並且,若是有一天須要重構DAO組件,只須提供修改過的DAO組件實現類,而業務邏輯組件無須任何改變。
業務邏輯組件代碼無須與DAO實現類耦合,業務邏輯組件的代碼面向DAO組件的接口編程,將業務邏輯組件和DAO組件的耦合下降到接口層次。
與此相似的是,業務邏輯組件徹底能夠採用這種編程模式,業務邏輯組件的配置文件代碼以下:
<?xml version="1.0" encoding="GBK"?>
<appContext>
<!-- 配置應用須要的業務邏輯組件,每一個業務邏輯組件對應一個app元素 -->
<app id="wawa" class="org.yeeku.service.impl.WawaServiceImpl"/>
</appContext>
業務邏輯組件工廠一樣可根據該配置文件來初始化全部業務邏輯組件,並將業務邏輯組件放入緩存池中,讓控制器與業務邏輯組件的耦合下降到接口層次。業務邏輯組件的工廠類代碼以下:
public class AppFactory
{
private Map<String , Object> appMap = new HashMap<String , Object>();
//業務邏輯組件工廠採用單態模式
private static AppFactory df;
//業務邏輯組件工廠的私有構造器
private AppFactory()throws Exception
{
//使用SAXReader來負責解析appContext.xml配置文檔
Document doc = new SAXReader().read(new File(ConstantsUtil.realPath
+ "\\appContext.xml"));
//獲取文檔的根文檔
Element root = doc.getRootElement();
//獲取appContext根元素的全部子元素
List el = root.elements();
for (Iterator it = el.iterator();it.hasNext() ; )
{
//每一個app元素對應一個業務邏輯組件
Element em = (Element)it.next();
String id = em.attributeValue("id");
//根據配置文件指定的業務邏輯組件實現類來建立業務邏輯組件實例
String impl = em.attributeValue("class");
Class implClazz = Class.forName(impl);
Object d = implClazz.newInstance();
//將業務邏輯組件放入緩存池中
appMap.put(id , d);
}
}
//單態模式必須提供入口方法,用於建立業務邏輯組件工廠
public static AppFactory instance()throws Exception
{
//若是業務邏輯組件工廠爲空
if (df == null)
{
df = new AppFactory();
}
return df;
}
//根據業務邏輯組件的id屬性獲取業務邏輯組件
public Object getApp(String id)
{
//直接從緩存池中取出業務邏輯組件,並返回
return appMap.get(id);
}
}
從某種程度上來說,這種方式與後來Spring的控制反轉(Inversion of Control,IoC)容器有殊途同歸之妙,但Spring的IoC容器則提供了更多的功能。
上面的兩個類中都用到了一個ConstantsUtil,它僅用於保存一個全局變量,有一個public static的realPath屬性,該屬性用於保存應用在服務器中的路徑。
posted @ 2009-07-19 10:08 jadmin 閱讀(1) 評論(0) 編輯
一般,Hibernate執行持久化過程當中,應用程序沒法參與其中。全部的數據持久化操做,對用戶都是透明的,用戶沒法插入本身的動做。
經過事件框架,Hibernate容許應用程序能響應特定的內部事件,從而容許實現某些通用的功能,或對Hibernate功能進行擴展。
Hibernate的事件框架由兩個部分組成:
● 攔截器機制,對於特定動做攔截,回調應用中的特定動做。
● 事件系統,重寫Hibernate的事件監聽器。
經過Interceptor接口,能夠從Session中回調應用程序的特定方法,這種回調機制可以讓應用程序在持久化對象被保存、更新、刪除或加載以前,檢查並修改其屬性。
經過Interceptor接口,能夠在數據進入數據庫之間,對數據進行最後的檢查,若是數據不符合要求,能夠修改數據,從而避免非法數據進入數據庫。固然,一般無須這樣作,只是在某些特殊的場合下,才考慮使用攔截器完成檢查功能。
使用攔截器可按以下步驟進行:
(1)定義實現Interceptor接口的攔截器類;
(2)經過Session啓用攔截器,或者經過Configuration啓用全局攔截器。
下面是一個攔截器的示例代碼,該攔截器沒有進行任何實際的操做,僅僅打印出標誌代碼:
public class MyInterceptor extends EmptyInterceptor
{
//更新的次數
private int updates;
//插入的次數
private int creates;
//刪除數據時,將調用onDelete方法
public void onDelete(Object entity,Serializable id,Object[]
state,String[] propertyNames, Type[] types)
{
//do nothing
}
//同步Session和數據庫中的數據
public boolean onFlushDirty(Object entity, Serializable id, Object[]
currentState, Object[] previousState, String[] propertyNames, Type[]
types)
{
//每同步一次,修改的累加器加1
updates++;
for ( int i=0; i < propertyNames.length; i++ )
{
if ( "lastUpdateTimestamp".equals( propertyNames[i] ) )
{
currentState[i] = new Date();
return true;
}
}
return false;
}
//加載持久化實例時,調用該方法
public boolean onLoad(Object entity,Serializable id,Object[]
state,String[] propertyNames,Type[] types)
{
System.out.println("========================");
for ( int i=0; i < propertyNames.length; i++ )
{
if ( "name".equals( propertyNames[i] ) )
{
System.out.println(state[i]);
state[i] = "aaa";
return true;
}
}
return false;
}
//保存持久化實例時,調用該方法
public boolean onSave(Object entity,Serializable id,Object[]
state,String[] propertyNames,Type[] types)
{
creates++;
for ( int i=0; i<propertyNames.length; i++ )
{
if ( "createTimestamp".equals( propertyNames[i] ) )
{
state[i] = new Date();
return true;
}
}
return false;
}
//提交刷新
public void postFlush(Iterator entities)
{
System.out.println("建立的次數: " + creates + ", 更新的次數: " +
updates);
}
public void preFlush(Iterator entities)
{
updates=0;
creates=0;
}
//事務提交前,觸發該方法
public void beforeTransactionCompletion(Transaction tx)
{
System.out.println("事務即將結束");
}
//事務提交後,觸發該方法
public void afterTransactionCompletion(Transaction tx)
{
System.out.println("事務已經結束");
}
}
在上面的攔截器實現類中,實現了不少方法,這些方法都是在Hibernate執行特定動做時自動調用。
完成了攔截器的定義,下面是關於攔截器的使用。攔截器的使用有兩種方法:
● 經過SessionFactory的openSession(Interceptor in)方法打開一個帶局部攔截器的Session。
● 經過Configuration的setInterceptor(Interceptor in)方法設置全局攔截器。
下面是使用局部攔截器的示例代碼:
public class HibernateUtil
{
//靜態類屬性 SessionFactory
public static final SessionFactory sessionFactory;
//靜態初始化塊,完成靜態屬性的初始化
static
{
try
{
//採用默認的hibernate.cfg.xml來啓動一個Configuration的實例
Configuration configuration=new Configuration().configure();
//由Configuration的實例來建立一個SessionFactory實例
sessionFactory = configuration.buildSessionFactory();
}
catch (Throwable ex)
{
System.err.println("初始化sessionFactory失敗." + ex);
throw new ExceptionInInitializerError(ex);
}
}
//ThreadLocal是隔離多個線程的數據共享,不存在多個線程之間共享資源,所以再也不須要
對線程同步
public static final ThreadLocal session = new ThreadLocal();
//不加攔截器的打開Session方法
public static Session currentSession() throws HibernateException
{
Session s = (Session) session.get();
//若是該線程尚未Session,則建立一個新的Session
if (s == null)
{
s = sessionFactory.openSession();
//將得到的Session變量存儲在ThreadLocal變量的Session裏
session.set(s);
}
return s;
}
//加攔截器的打開Session方法
public static Session currentSession(Interceptor it) throws
HibernateException
{
Session s = (Session) session.get();
//若是該線程尚未Session,則建立一個新的Session
if (s == null)
{
//以攔截器建立Session對象
s = sessionFactory.openSession(it);
//將得到的Session變量存儲在ThreadLocal變量的Session裏
session.set(s);
}
return s;
}
//關閉Session對象
public static void closeSession() throws HibernateException
{
Session s = (Session) session.get();
if (s != null)
s.close();
session.set(null);
}
}
上面的Hibernate工具類提供了兩個currentSession方法,分別用於不使用攔截器獲取Session對象和使用攔截器獲取Session對象。
下面是主程序使用攔截器的代碼片斷:
private void testUser()
{
//以攔截器開始Session
Session session = HibernateUtil.currentSession(new MyInterceptor());
//開始事務
Transaction tx = session.beginTransaction();
//執行下面的代碼時,能夠看到系統回調onSave等方法
/*
User u = new User();
u.setName("Yeeku Lee");
u.setAge(28);
u.setNationality("中國");
session.persist(u);
u.setAge(29);
u.setAge(30);
session.persist(u);
*/
//執行下面的代碼時,能夠看到系統回調onLoad等方法
Object o = session.load(User.class , new Integer(1));
System.out.println(o);
User u = (User)o;
System.out.println(u.getName());
//提交事務時,能夠看到系統回調事務相關方法
tx.commit();
HibernateUtil.closeSession();
}
Hibernate 3的事件系統是功能更強大的事件框架,事件系統能夠替代攔截器,也能夠做爲攔截器的補充來使用。
基本上,Session接口的每一個方法都有對應的事件。如LoadEvent和FlushEvent等。當Session調用某個方法時,Hibernate Session會生成對應的事件,並激活對應的事件監聽器。
系統默認監聽器實現的處理過程,完成了全部的數據持久化操做,包括插入和修改等操做。若是用戶定義了本身的監聽器,則意味着用戶必須完成對象的持久化操做。
例如,能夠在系統中實現並註冊LoadEventListener監聽器,該監聽器負責處理全部調用Session的load()方法的請求。
監聽器是單態模式對象,即全部同類型的事件處理共享同一個監聽器實例,所以監聽器不該該保存任何狀態,即不該該使用成員變量。
使用事件系統可按以下步驟進行:
(1)實現本身的事件監聽器類;
(2)註冊自定義事件監聽器,代替系統默認的事件監聽器。
實現用戶的自定義監聽器有以下3個方法:
● 實現對應的監聽器接口,這是難以想象的,實現接口必須實現接口內的全部方法,關鍵是必須實現Hibernate對應的持久化操做,即數據庫訪問,這意味着程序員徹底取代了Hibernate的底層操做。
● 繼承事件適配器,能夠選擇性地實現須要關注的方法,但依然試圖取代Hibernate完成數據庫的訪問,這也不太現實。
● 繼承系統默認的事件監聽器,擴展特定方法。
實際上,前兩種方法不多使用。由於Hibernate的持久化操做也是經過這些監聽器實現的,若是用戶取代了這些監聽器,則應該本身實現全部的持久化操做,這意味着用戶放棄了Hibernate的持久化操做,而改成本身完成Hibernate的核心操做。
一般推薦採用第三種方法實現本身的事件監聽器。Hibernate默認的事件監聽器都被聲明成non-final,從而方便用戶繼承。
下面是用戶自定義監聽器的示例:
//自定義LoadListener,繼承默認的DefaultLoadEventListener實現類
public class MyLoadListener extends DefaultLoadEventListener
{
//在LoadEventListener接口僅僅定義了這個方法
public Object onLoad(LoadEvent event, LoadEventListener.LoadType
loadType)throws HibernateException
{
//先調用父類的onLoad方法,從而完成默認的持久化操做
Object o = super.onLoad(event, loadType);
//加入用戶的自定義處理
System.out.println("自定義的load事件");
System.out.println(event.getEntityClassName() + "==========" +
event.getEntityId());
return o;
}
}
下面還有一個MySaveListener,用於監聽SaveEvent事件:
//自定義SavaListener,繼承默認的DefaultSaveEventListener實現類
public class MySaveListener extends DefaultSaveEventListener
{
//該方法完成實際的數據插入動做
protected Serializable performSaveOrUpdate(SaveOrUpdateEvent event)
{
//先執行用戶自定義的操做
System.out.println(event.getObject());
//調用父類的默認持久化操做
return super.performSaveOrUpdate(event);
}
}
注意:擴展用戶自定義監聽器時,別忘了在方法中調用父類的對應方法。
註冊用戶自定義監聽器也有兩種方法:
● 編程式,經過使用Configuration對象編程註冊。
● 聲明式,在Hibernate的XML格式配置文件中進行聲明,使用Properties格式的配置文件將沒法配置自定義監聽器。
下面的示例代碼,經過編程方式使用自定義監聽器:
public class HibernateUtil2
{
//靜態類屬性 SessionFactory
public static final SessionFactory sessionFactory;
//靜態初始化塊,完成靜態屬性的初始化
static
{
try
{
Configuration cfg = new Configuration();
//註冊loadEventListener監聽器
cfg.getSessionEventListenerConfig().setLoadEventListener
( new MyLoadListener() );
//註冊saveListener監聽器
cfg.getSessionEventListenerConfig().setSaveEventListener
(new MySaveListener() );
//由Configuration實例來建立一個SessionFactory實例
sessionFactory = cfg.configure().buildSessionFactory();
}
catch (Throwable ex)
{
System.err.println("初始化sessionFactory失敗." + ex);
throw new ExceptionInInitializerError(ex);
}
}
//ThreadLocal是隔離多個線程的數據共享,不存在多個線程之間共享資源,所以再也不須要
對線程同步
public static final ThreadLocal session = new ThreadLocal();
//不加攔截器的打開Session方法
public static Session currentSession() throws HibernateException
{
Session s = (Session) session.get();
//若是該線程尚未Session,則建立一個新的Session
if (s == null)
{
s = sessionFactory.openSession();
//將得到的Session變量存儲在ThreadLocal變量的Session裏
session.set(s);
}
return s;
}
//關閉Session對象
public static void closeSession() throws HibernateException
{
Session s = (Session) session.get();
if (s != null)
s.close();
session.set(null);
}
}
若是不想修改代碼,也能夠在配置文件中使用事件監聽器,註冊事件監聽器的Hibernate配置文件代碼以下:
<?xml version='1.0' encoding='GBK'?>
<!-- Hibernate配置文件的文件頭,包含DTD等信息 -->
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.
dtd">
<!-- Hibernate配置文件的根元素 -->
<hibernate-configuration>
<session-factory>
<!—設置數據庫驅動 -->
<property name="connection.driver_class">com.mysql.jdbc.Driver
</property>
<!-- 數據庫服務的url -->
<property name="connection.url">jdbc:mysql://localhost/hibernate
</property>
<!-- 數據庫服務的用戶名 -->
<property name="connection.username">root</property>
<!-- 數據庫服務的密碼 -->
<property name="connection.password">32147</property>
<!-- JDBC connection pool (use the built-in) -->
<property name="connection.pool_size">5</property>
<!-- 設置數據庫方言 -->
<property name="dialect">org.hibernate.dialect.MySQLDialect
</property>
<!-- 顯示Hibernate生成的SQL語句 -->
<property name="show_sql">true</property>
<!-- 配置應用啓動時,是否啓動自動建表 -->
<property name="hbm2ddl.auto">update</property>
<!-- 列出全部的持久化映射文件 -->
<mapping resource="User.hbm.xml"/>
<!-- 註冊事件監聽器 -->
<listener type="load" class="lee.MyLoadListener"/>
<listener type="save" class="lee.MySaveListener"/>
</session-factory>
</hibernate-configuration>
使用配置文件註冊事件監聽器雖然方便,但也有不利之處,經過配置文件註冊的監聽器不能共享實例。若是多個<listener/>元素中使用了相同的類,則每個引用都將產生一個新的攔截器實例。若是須要在多個事件之間共享監聽器的實例,則必須使用編程方式註冊事件監聽器。
注意:雖然監聽器類實現了特定監聽器的接口,在註冊的時候還要明確指出註冊的事件。這是由於一個類可能實現多個監聽器的接口,註冊時明確指定要監聽的事件,能夠使得啓用或者禁用某個事件監聽的配置工做更簡單。
posted @ 2009-07-19 09:42 jadmin 閱讀(2) 評論(0) 編輯
每一個業務邏輯方法都是由一系列的數據庫訪問完成,這一系列的數據訪問可能會修改多條數據記錄,這系列的修改應該是一個總體,毫不能僅修改其中的幾條。也就是說,多個數據庫原子訪問應該綁定成一個總體——這就是事務。事務是一個最小的邏輯執行單元,整個事務不能分開執行,要麼同時執行,要麼同時放棄執行。
事務是一步或幾步基本操做組成的邏輯執行單元,這些基本操做做爲一個總體執行單元,它們要麼所有執行,要麼所有取消,毫不能僅僅執行部分。通常而言,每次用戶請求,對應一個業務邏輯方法,一個業務邏輯方法每每具備邏輯上的原子性,應該使用事務。例如,一個轉帳操做,對應修改兩個帳戶的餘額,這兩個帳戶的修改要麼同時生效,要麼同時取消——同時生效是轉帳成功,同時取消是轉帳失敗;但不可只修改其中一個帳戶,那將破壞數據庫的完整性。
一般來說,事務具有以下4個特性:原子性(atomicity)、一致性(consistency)、隔離性(isolation)和持續性(durability)。這4個特性也簡稱爲ACID性。
● 原子性:事務是應用中最小執行單位,就如原子是天然界最小顆粒,具備不可再分的特徵同樣。事務是應用中不可再分的最小邏輯執行體。
● 一致性:事務執行的結果,必須使數據庫從一個一致性狀態,變到另外一個一致性狀態。當數據庫只包含事務成功提交的結果時,數據庫處於一致性狀態。若是系統運行發生中斷,某個事務還沒有完成而被迫中斷,而該未完成的事務對數據庫所作的修改已被寫入數據庫,此時,數據庫就處於一種不正確的狀態。好比銀行在兩個帳戶之間轉帳,從A帳戶向B帳戶轉入1000元。系統先減小A帳戶的1000元,而後再爲B帳戶增長1000元。若是所有執行成功,數據庫處於一致性狀態。若是僅執行完A帳戶金額的修改,而沒有增長B帳戶的金額,則數據庫就處於不一致性狀態。所以,一致性是經過原子性來保證的。
● 隔離性:各個事務的執行互不干擾,任意一個事務的內部操做對其餘併發的事務,都具備隔離性。也即併發執行的事務之間不能互相影響。
● 持續性:持續性也稱爲持久性(persistence),指事務一旦提交,對數據所作的任何改變,都要記錄到永久存儲器中,一般保存進物理數據庫。
Hibernate直接使用JDBC鏈接和JTA資源,不添加任何附加鎖定行爲。Hibernate只添加自動版本管理,而不會鎖定內存中的對象,也不會改變數據庫事務的隔離級別。基本上,使用 Hibernate就好像直接使用JDBC(或者JTA/CMT)進行數據庫訪問。
Hibernate中SessionFactory對象的建立代價很高,它是線程安全的對象,被設計成能夠爲全部的應用程序線程所共享。一般,SessionFactory會在應用程序啓動時建立,一旦建立了SessionFactory將不會輕易關閉,只有當應用關閉時,SessionFactory纔會關閉。
而Session的對象是輕量級的,它也是線程不安全的。對於單個業務進程單個工做單元而言,Session只被使用一次。建立Session時,並不會當即打開與數據庫之間的鏈接,Session只在須要進行數據庫操做時,纔會獲取JDBC鏈接。所以,打開和關閉Session,並不會對性能形成很大的影響。甚至即便沒法肯定一個請求是否須要數據訪問,也能夠打開Session對象,由於若是不進行數據庫訪問,Session不會獲取JDBC鏈接。
相反,數據庫事務應該儘量的短。從而,下降數據庫鎖定形成的資源爭用。數據庫長事務會致使應用程序沒法承載高併發的負荷。
由上面的介紹可知,Hiberante的Session和事務是緊密相關的,由於事務是經過Session來打開的。那麼事務的範圍是多大?單個Session能夠跨越多個數據庫事務嗎?事務和Session的對應關係又如何呢?下面將介紹Hibernate Session和事務的關係。
數據庫操做必須在Hibernate的Session管理下進行,但不推薦由於一次簡單的數據庫原子調用,就打開和關閉一次Session,數據庫事務也是如此。由於,對於一次原子操做打開的事務沒有任何意義——事務應該是將多個操做步驟組合成一個邏輯總體。
事務是按順序發送並組成一個邏輯總體的原子操做單元。
注意:也就是說單個的SQL語句發送以後,自動事務提交模式失效了。這種自動提交模式僅爲SQL控制檯設計,在實際項目沒有太大的實用價值。Hibernate禁止事務當即自動提交模式,或者讓應用服務器禁止事務自動提交。
一般,建議每一個請求對應一個Session。在這種模式下,來自客戶端的請求被髮送到服務器端,此處可能對應一個業務邏輯方法。在這個業務邏輯方法內,一個新的Hibernate Session被打開,而後開始事務,在事務內執行這個操做單元中全部的數據庫操做。一旦操做完成,須要發送給客戶端的響應也準備就緒。此時,提交事務,而後關閉Session。在這種模式下,Session和用戶請求是一對一的關係,這是一種理想的Session管理模式。
爲了達到這種效果,推薦使用一個ThreadLocal變量,把Session綁定處處理客戶端請求的線程上去。這種方式可讓運行在該線程上的全部程序代碼輕鬆地訪問Session。也能夠在一個ThreadLocal變量中保持事務上下文環境,不過這依賴於所選擇的數據庫事務劃分機制。這種實現模式被稱之爲ThreadLocal Session和Open Session in View。
下面是一個HibernateUtil類,該類將Hibernate Session存放在一個ThreadLocal變量中,對於同一個線程的請求,將能夠輕鬆訪問該Session。
public class HibernateUtil
{
public static final SessionFactory sessionFactory;
//靜態初始化塊,使用該類時使用該代碼塊
static
{
try
{
//採用默認的hibernate.cfg.xml來啓動一個Configuration的實例
Configuration configuration=new Configuration().configure();
//由Configuration的實例來建立一個SessionFactory實例
sessionFactory = configuration.buildSessionFactory();
}
catch (Throwable ex)
{
System.err.println("初始化sessionFactory失敗." + ex);
throw new ExceptionInInitializerError(ex);
}
}
//ThreadLocal是隔離多個線程的數據共享,不存在多個線程之間共享資源,所以再也不須要
對線程同步
public static final ThreadLocal session = new ThreadLocal();
//該方法用於獲取當前線程的Session對象
public static Session currentSession() throws HibernateException
{
Session s = (Session) session.get();
//若是該線程尚未Session,則建立一個新的Session
if (s == null)
{
s = sessionFactory.openSession();
//將得到的Session變量存儲在ThreadLocal變量的Session裏
session.set(s);
}
return s;
}
//該方法用於關閉當前線程裏的Session
public static void closeSession() throws HibernateException
{
Session s = (Session) session.get();
if (s != null)
s.close();
session.set(null);
}
}
在上面的代碼中,Hibernate Session被綁定到當前線程。當調用currentSession方法時,若是當前線程中的Session已經建立出來,那麼將返回這個已經存在的Session實例。
每次請求對應一個Session的模式不只能夠用於設計操做單元,甚至不少業務處理流程都須要組合一系列的用戶操做,即用戶對數據庫的交叉訪問。
可是,對於企業應用,跨用戶交互的數據庫事務是沒法接受的。例如,在第一個頁面,用戶打開對話框,打開一個特定Session裝入的數據,能夠隨意修改對話框中的數據,修改完成後,將修改結果存入數據庫。
從用戶的角度來看,這個操做單元被稱爲應用程序長事務。在一個J2EE應用實現中,能夠有不少方法來實現這種應用程序長事務。
一個比較差的作法是,當用戶思考時,應用程序保持Session和數據庫事務是打開的,並保持數據庫鎖定,以阻止併發修改,從而保證數據庫事務隔離級別和原子操做。這種數據庫鎖定會致使應用程序沒法擴展併發用戶的數目。
所以,不要使用每一個應用對應一次Hibernate Session的模式,也不要使用每次Http Session對應一次Hibernate Session的模式。
注意:幾乎全部狀況下,都不要使用每一個應用對應一次Hibernate Session的模式,也不要使用每次Http Session對應一次Hibernate Session的模式。
對於這種狀況,Hibernate主要有以下兩種模式來解決這個問題:
● 脫管對象,若是採用每次用戶請求對應一次Session的模式。那麼,前面載入的實例在用戶思考的過程當中,始終與Session脫離,處於脫管狀態。都處於與Session脫離的狀態。Hibernate容許把脫管對象從新關聯到Session上,而且對修改進行持久化。在這種模式下,自動版本化被用來隔離併發修改。這種模式也被稱爲使用脫管對象的每一個請求對應一個Hibernate Session。
● 長生命週期Session,Session能夠在數據庫事務提交以後,斷開和底層的JDBC鏈接。當新的客戶端請求到來時,它又從新鏈接上底層的JDBC鏈接。這種模式被稱爲每一個應用程序事務對應一個Session,由於應用程序事務至關長(跨越多個用戶請求),因此也被稱爲每次應用事務對應一個Hibernate Session。
posted @ 2009-07-19 09:11 jadmin 閱讀(5) 評論(0) 編輯
數據過濾並非一種常規的數據查詢方法,而是一種總體的篩選方法。數據過濾也可對數據進行篩選,所以,將其放在Hibernate的數據查詢框架中介紹。
若是一旦啓用了數據過濾器,則無論數據查詢,仍是數據加載,該過濾器將自動做用於全部數據,只有知足過濾條件的記錄纔會被選出來。
過濾器與定義在類和集合映射文件上的「where」屬性很是類似。它們的區別是過濾器能夠帶參數,應用程序能夠在運行時決定是否啓用指定的過濾器,以及使用什麼樣的參數值。而映射文件上的「where」屬性將一直生效,且沒法動態傳入參數。
過濾器的用法很像數據庫視圖,區別是視圖在數據庫中已經定義完成,而過濾器則還需在應用程序中肯定參數值。
過濾器的使用分紅三步:
(1)定義過濾器。使用filter-def元素定義過濾器;
(2)使用過濾器。使用filter元素使用過濾器;
(3)在代碼中啓用過濾器。
前兩個步驟都是在Hibernate的映射文件中完成的,其中filter-def是hibernate-mapping元素的子元素,而filter元素是class集合元素的子元素。
filter-def元素用於定義一個過濾器,filter則將指定的過濾器應用到指定的持久化類。
一個持久化類或集合能夠使用多個過濾器,而一個過濾器也能夠做用於多個持久化類或集合。
看下面的映射文件示例:
<?xml version="1.0"?>
<!-- Hibernate配置文件的文件頭,包含DTD等信息 -->
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Hibernate 配置文件的根元素 -->
<hibernate-mapping >
<!-- 每一個class元素定義一個持久化類 -->
<class name="Category" table="category">
<!-- 定義標識屬性 -->
<id name="id" column="category_id" >
<!-- 指定主鍵生成器策略 -->
<generator class="native"/>
</id>
<!-- 映射name屬性 -->
<property name="name" type="string"/>
<!-- 映射effectiveStartDate屬性 -->
<property name="effectiveStartDate" column="eff_start_date"
type="java.util.Date"/>
<!-- 映射effectiveEndDate屬性 -->
<property name="effectiveEndDate" column="eff_end_date"
type="java.util.Date"/>
<!-- 映射N-N關聯屬性 -->
<set cascade="none" inverse="true" name="products"
table="product_category">
<!-- 定義關聯屬性的key,對應鏈接表中的外鍵列 -->
<key column="category_id"/>
<!-- 定義關聯屬性 -->
<many-to-many column="product_id" class="Product"/>
</set>
<!-- 使用過濾器,並設置過濾器條件 -->
<filter name="effectiveDate" condition=":asOfDate BETWEEN
eff_start_date and eff_end_date"/>
</class>
<!-- 定義第二個持久化類 -->
<class name="Product" table="product">
<!-- 定義標識屬性 -->
<id name="id" column="product_id" >
<!-- 指定主鍵生成器策略 -->
<generator class="native"/>
</id>
<!-- 映射name屬性 -->
<property name="name" type="string"/>
<!-- 映射stockNumber屬性 -->
<property name="stockNumber" column="stock_number" type="int"/>
<!-- 映射effectiveStartDate屬性 -->
<property name="effectiveStartDate" column="eff_start_date"
type="java.util.Date"/>
<!-- 映射effectiveEndDate屬性 -->
<property name="effectiveEndDate" column="eff_end_date"
type="java.util.Date"/>
<!-- 映射N-N關聯屬性 -->
<set cascade="all" name="categories" fetch="join"
table="product_category" >
<!-- 定義關聯屬性的key,對應鏈接表中的外鍵列 -->
<key column="product_id"/>
<!-- 定義關聯屬性 -->
<many-to-many column="category_id"
class="Category" fetch="join">
<!-- 對關聯屬性使用第一個過濾器 -->
<filter name="effectiveDate"
condition=":asOfDate BETWEEN eff_start_date and
eff_end_date"/>
<!-- 對關聯屬性使用第二個過濾器 -->
<filter name="category" condition="category_id = :catId"/>
</many-to-many>
</set>
<filter name="effectiveDate" condition=":asOfDate BETWEEN
eff_start_date AND eff_end_date"/>
</class>
<!-- 定義第一個過濾器,該過濾器包含一個date類型的參數 -->
<filter-def name="effectiveDate">
<filter-param name="asOfDate" type="date"/>
</filter-def>
<!-- 定義第二個過濾器,該過濾器包含一個long類型的參數 -->
<filter-def name="category">
<filter-param name="catId" type="long"/>
</filter-def>
</hibernate-mapping>
在上面的配置文件中,定義了兩個過濾器,過濾器的定義經過filter-def元素完成。定義過濾器時,只須要指定過濾器的名字,以及過濾器的參數便可。如Java裏的一個方法聲明,只有方法名和參數列表,具體的方法實現是沒有的。
過濾器的過濾條件是使用過濾器時才肯定的,使用過濾器經過filter元素肯定,filter的condition屬性用於肯定過濾條件,知足該條件的記錄纔會被抓取到。
系統默認不啓用過濾器,必須顯式經過enableFilter(String filterName)才能夠啓用過濾器,該方法返回一個Filter實例,Filter包含setParameter方法用於爲過濾器參數賦值。
一旦啓用了過濾器,過濾器在整個Session內有效,全部的數據加載將自動應用該過濾條件,直到調用disableFilter方法。
看下面的使用過濾器的示例代碼:
private void test() throws Exception
{
//獲取Hibernate Session對象
Session session = HibernateUtil.currentSession();
//開始事務
Transaction tx = session.beginTransaction();
//啓用第一個過濾器
session.enableFilter("effectiveDate")
//爲過濾器設置參數
.setParameter("asOfDate", new Date());
//啓動第二個過濾器
session.enableFilter("category")
//爲過濾器設置參數
.setParameter("catId", new Long(2));
//執行查詢,該查詢沒有任何的查詢條件
Iterator results = session.createQuery("from Product as p")
.iterate();
//遍歷結果集
while (results.hasNext())
{
Product p = (Product)results.next();
System.out.println(p.getName());
//此處獲取Product關聯的種類,過濾器也將自動應用過濾
Iterator it = p.getCategories().iterator();
System.out.println(p.getCategories().size());
while (it.hasNext())
{
Category c = (Category)it.next();
System.out.println(c.getName());
}
}
tx.commit();
HibernateUtil.closeSession();
}
經過使用過濾器定義經常使用的數據篩選規則,若是是臨時的數據篩選,仍是使用常規查詢比較好。對於從前使用行列表達式視圖的地方,此處能夠考慮使用過濾器。
posted @ 2009-07-19 09:08 jadmin 閱讀(0) 評論(0) 編輯
Hibernate還支持使用SQL查詢,使用SQL查詢能夠利用某些數據庫的特性,或者用於將原有的JDBC應用遷移到Hibernate應用上。使用命名的SQL查詢還能夠將SQL語句放在配置文件中配置,從而提升程序的解耦,命名SQL查詢還能夠用於調用存儲過程。
若是是一個新的應用,一般不要使用SQL查詢。
SQL查詢是經過SQLQuery接口來表示的,SQLQuery接口是Query接口的子接口,所以徹底能夠調用Query接口的方法:
● setFirstResult(),設置返回結果集的起始點。
● setMaxResults(),設置查詢獲取的最大記錄數。
● list(),返回查詢到的結果集。
但SQLQuery比Query多了兩個重載的方法:
● addEntity,將查詢到的記錄與特定的實體關聯。
● addScalar,將查詢的記錄關聯成標量值。
執行SQL查詢的步驟以下:
(1)獲取Hibernate Session對象;
(2)編寫SQL語句;
(3)以SQL語句做爲參數,調用Session的createSQLQuery方法建立查詢對象;
(4)若是SQL語句包含參數,調用Query的setXxx方法爲參數賦值;
(5)調用SQLQuery對象的addEntity或addScalar方法將選出的結果與實體或標量值關聯;
(6)調用Query的list方法返回查詢的結果集。
看下面的SQL查詢示例:
private void test()
{
//獲取Hibernate Session對象
Session session = HibernateUtil.currentSession();
//開始事務
Transaction tx = session.beginTransaction();
//編寫SQL語句
String sqlString = "select {s.*} from student s where s.name like '馬軍'";
//以SQL語句建立SQLQuery對象
List l = session.createSQLQuery(sqlString)
//將查詢到的記錄與特定實體關聯起來
.addEntity("s",Student.class)
//返回所有的記錄集
.list();
//遍歷結果集
Iterator it = l.iterator();
while (it.hasNext())
{
//由於將查詢結果與Student類關聯,所以返回的是Student集合
Student s = (Student)it.next();
Set enrolments = s.getEnrolments();
Iterator iter = enrolments.iterator();
while(iter.hasNext())
{
Enrolment e = (Enrolment)iter.next();
System.out.println(e.getCourse().getName());
}
}
//提交事務
tx.commit();
//關閉Session
HibernateUtil.closeSession();
}
上面的示例顯示了將查詢記錄關聯成一個實體的示例。事實上,SQL查詢也支持將查詢結果轉換成標量值,轉換成標量值能夠使用addScalar方法,如:
Double max = (Double) session.createSQLQuery("select max(cat.weight) as maxWeight from cats cat")
.addScalar("maxWeight", Hibernate.DOUBLE);
.uniqueResult();
使用SQL查詢,若是須要將查詢到的結果轉換成特定實體,就要求爲選出的字段命名別名。這別名不是隨意命名的,而是以「/」實例名.屬性名「/」的格式命名,例如:
//依次將多個選出的字段命名別名,命名別名時都以ss做爲前綴,ss是關聯實體的別名
String sqlStr = "select stu.studentId as {ss.studentNumber},"
+ "stu.name as {ss.name} from "
+ "student as stu where stu.name like '楊海華'";
List l = session.createSQLQuery(sqlStr)
//將查詢出的ss實例,關聯到Student類
.addEntity("ss",Student.class)
.list();
在第一個示例中,以{s.*}表明該表的所有字段,且關聯實例的別名也被指定爲s。
注意:若是不使用{s.*}的形式,就可以讓實體別名和表別名互不相同。關聯實體的類型時,被關聯的類必須有對應的setter方法。
能夠將SQL語句不放在程序中,而放在配置文件中,這種方式以鬆耦合的方式配置SQL語句,能夠提升程序解耦。
在Hibernate的映射文件中定義查詢名,而後肯定查詢所用的SQL語句,而後就能夠直接調用該命名SQL查詢。在這種狀況下,不須要調用addEntity()方法,由於在配置命名SQL查詢時,已經完成了查詢結果與實體的關聯。
下面是命名SQL查詢的配置片斷:
<!-- 每一個sql-query元素定義一個命名SQL查詢 -->
<sql-query name="mySqlQuery">
<!-- 關聯返回的結果與實體類 -->
<return alias="s" class="Student"/>
<!-- 定義命名SQL查詢的SQL語句 -->
SELECT {s.*}
from student s WHERE s.name like'楊海華'
</sql-query>
sql-query元素是hibernate-mapping元素的子元素。所以,sql-query定義的名能夠直接經過Session訪問,上面定義的mySqlQuery查詢能夠直接訪問,下面是使用該命名SQL查詢的示例代碼:
private void testNamedSQl()
{
//獲取Hibernate Session對象
Session session = HibernateUtil.currentSession();
//開始事務
Transaction tx = session.beginTransaction();
//調用命名查詢,直接返回結果
List l = session.getNamedQuery("mySqlQuery")
.list();
//遍歷結果集
Iterator it = l.iterator();
while (it.hasNext())
{
//在定義SQL查詢時,已經將結果集與Student類關聯起來
//所以,集合裏的每一個元素都是Student實例
Student s = (Student)it.next();
Set enrolments = s.getEnrolments();
Iterator iter = enrolments.iterator();
while(iter.hasNext())
{
Enrolment e = (Enrolment)iter.next();
System.out.println("=====================================");
System.out.println(e.getCourse().getName());
System.out.println("=====================================");
}
}
tx.commit();
HibernateUtil.closeSession();
}
Hibernate 3增長了存儲過程的支持,該存儲過程只能返回一個結果集。
下面是Oracle 9i的存儲過程示例:
CREATE OR REPLACE FUNCTION selectAllEmployments
RETURN SYS_REFCURSOR
AS
st_cursor SYS_REFCURSOR;
BEGIN
OPEN st_cursor FOR
SELECT EMPLOYEE, EMPLOYER,
STARTDATE, ENDDATE,
REGIONCODE, EID, VALUE, CURRENCY
FROM EMPLOYMENT;
RETURN st_cursor;
END;
若是須要使用該存儲過程,能夠先將其定義成命名SQL查詢,例如:
<!-- 定義命名SQL查詢,name屬性指定命名SQL查詢名 -->
<sql-query name="selectAllEmployees_SP" callable="true">
<!-- 定義返回列與關聯實體類屬性之間的映射 -->
<return alias="emp" class="Employment">
<!-- 依次定義每列與實體類屬性的對應 -->
<return-property name="employee" column="EMPLOYEE"/>
<return-property name="employer" column="EMPLOYER"/>
<return-property name="startDate" column="STARTDATE"/>
<return-property name="endDate" column="ENDDATE"/>
<return-property name="regionCode" column="REGIONCODE"/>
<return-property name="id" column="EID"/>
<!-- 將兩列值映射到一個關聯類的組件屬性 -->
<return-property name="salary">
<!-- 映射列與組件屬性之間的關聯 -->
<return-column name="VALUE"/>
<return-column name="CURRENCY"/>
</return-property>
</return>
{ ? = call selectAllEmployments() }
</sql-query>
調用存儲過程還有以下須要注意的地方:
● 由於存儲過程自己完成了查詢的所有操做,因此調用存儲過程進行的查詢沒法使用setFirstResult()/setMaxResults()進行分頁。
● 存儲過程只能返回一個結果集,若是存儲過程返回多個結果集,Hibernate將僅處理第一個結果集,其餘將被丟棄。
● 若是在存儲過程裏設定SET NOCOUNT ON,將有更好的性能表現。固然也能夠沒有該設定。
posted @ 2009-07-19 09:02 jadmin 閱讀(1) 評論(0) 編輯
條件查詢是更具面向對象特點的數據查詢方式。條件查詢可經過以下3個類完成:
● Criteria,表明一次查詢。
● Criterion,表明一個查詢條件。
● Restrictions,產生查詢條件的工具類。
執行條件查詢的步驟以下:
(1)得到Hibernate的Session對象。
(2)以Session對象建立Criteria對象。
(3)增長Criterion查詢條件。
(4)執行Criteria的list等方法返回結果集。
看下面的條件查詢示例:
private void test()
{
//獲取Hibernate Session對象
Session session = HibernateUtil.currentSession();
//開始事務
Transaction tx = session.beginTransaction();
//建立Criteria和添加查詢條件同步完成
//最後調用list方法,返回查詢到的結果集
List l = session.createCriteria(Student.class)
//此處增長限制條件必須是Student已經存在的屬性
.add( Restrictions.gt("studentNumber" , new Long(20050231) ) )
//若是要增長對Student的關聯類的屬性的限制則必須從新createCriteria()
/若是此關聯屬性是集合,則只要集合裏任意一個對象的屬性知足下面條件
.createCriteria("enrolments")便可
.add( Restrictions.gt("semester" , new Short("2") ) )
.list();
Iterator it = l.iterator();
//遍歷查詢到的記錄
while (it.hasNext())
{
Student s = (Student)it.next();
System.out.println(s.getName());
Set enrolments = s.getEnrolments();
Iterator iter = enrolments.iterator();
while(iter.hasNext())
{
Enrolment e = (Enrolment)iter.next();
System.out.println(e.getCourse().getName());
}
}
tx.commit();
ibernateUtil.closeSession();
}
在條件查詢中,Criteria接口表明一次查詢,該查詢自己不具有任何的數據篩選功能,Session調用createCriteria(Class clazz)方法對某個持久化類建立條件查詢實例。
Criteria包含以下兩個方法:
● Criteria setFirstResult(int firstResult),設置查詢返回的第一行記錄。
● Criteria setMaxResults(int maxResults),設置查詢返回的記錄數。
這兩個方法與Query的這兩個方法用法類似,都用於完成查詢分頁。
而Criteria還包含以下經常使用方法:
● Criteria add(Criterion criterion),增長查詢條件。
● Criteria addOrder(Order order),增長排序規則。
● List list(),返回結果集。
Criterion接口表明一個查詢條件,該查詢條件由Restrictions負責產生,Restrictions是專門用於產生查詢條件的工具類,它的方法大部分都是靜態方法,經常使用的方法以下:
● static Criterion allEq(Map propertyNameValues),判斷指定屬性(由Map參數的key指定)和指定值(由Map參數的value指定)是否徹底相等。
● static Criterion between(String propertyName,Object lo, Object hi),判斷屬性值在某個值範圍以內。
● static Criterion ilike(String propertyName, Object value),判斷屬性值匹配某個字符串。
● static Criterion ilike(String propertyName, String value,MatchMode matchMode),判斷屬性值匹配某個字符串,並肯定匹配模式。
● static Criterion in(String propertyName,Collection values),判斷屬性值在某個集合內。
● static Criterion in(String propertyName,Object[] values),判斷屬性值是數組元素的其中之一。
● static Criterion isEmpty(String propertyName),判斷屬性值是否爲空。
● static Criterion isNotEmpty(String propertyName),判斷屬性值是否不爲空。
● static Criterion isNotNull(String propertyName),判斷屬性值是否爲空。
● static Criterion isNull(String propertyName),判斷屬性值是否不爲空。
● static Criterion not(Criterion expression),對Criterion求否。
● static Criterion sizeEq(String propertyName, int size),判斷某個屬性的元素個數是否與size相等。
● static Criterion sqlRestriction(String sql),直接使用SQL語句做爲篩選條件。
● static Criterion sqlRestriction(String sql, Object[] values, Type[] types),直接使用帶參數佔位符的SQL語句做爲條件,並指定多個參數值。
● static Criterion sqlRestriction(String sql, Object value, Type type),直接使用帶參數佔位符的SQL語句做爲條件,並指定參數值。
Order實例表明一個排序標準,Order有以下構造器:
Order(String propertyName, boolean ascending),根據propertyName排序,是否採用升序,若是後一個參數爲true,採用升序排序,不然採用降序排序。
若是須要使用關聯類的屬性來增長查詢條件,則應該對屬性再次使用createCriteria方法。看以下示例:
session.createCriteria(Person.class)
.add(Restrictions.like("name" , "dd%"))
.createCriteria("addresses")
.add(Restrictions.like("addressdetail" , "上海%"))
.list();
上面的代碼表示創建Person類的條件查詢,第一個查詢條件是直接過濾Person的屬性,即選出name屬性以dd開始的Person實例,第二個查詢條件則過濾Person關聯實例的屬性,其中addresses是Person類的關聯持久化類Address,而addressdetail則是Address類的屬性。值得注意的是,查詢並非查詢Address持久化類,而是查詢Person持久化類。
注意:使用關聯類的條件查詢,依然是查詢原有持久化類的實例,而不是查詢被關聯類的實例。
posted @ 2009-07-19 08:59 jadmin 閱讀(5) 評論(0) 編輯
Hibernate提供了異常強大的查詢體系,使用Hibernate有多種查詢方式。能夠選擇使用Hibernate的HQL查詢,或者使用條件查詢,甚至能夠使用原生的SQL查詢語句,此外還提供了一種數據過濾功能,這些均可用於篩選目標數據。
下面分別介紹Hibernate的4種數據篩選方法:
HQL是Hibernate Query Language的縮寫,HQL的語法很像SQL的語法,但HQL是一種面向對象的查詢語言。所以,SQL的操做對象是數據表和列等數據對象,而HQL的操做對象是類、實例、屬性等。
HQL是徹底面向對象的查詢語言,所以能夠支持繼承和多態等特徵。
HQL查詢依賴於Query類,每一個Query實例對應一個查詢對象。使用HQL查詢可按以下步驟進行:
(1)獲取Hibernate Session對象;
(2)編寫HQL語句;
(3)以HQL語句做爲參數,調用Session的createQuery方法建立查詢對象;
(4)若是HQL語句包含參數,調用Query的setXxx方法爲參數賦值;
(5)調用Query對象的list等方法遍歷查詢結果。
看下面的查詢示例:
public class HqlQuery
{
public static void main(String[] args)throws Exception
{
HqlQuery mgr = new HqlQuery();
//調用查詢方法
mgr.findPersons();
//調用第二個查詢方法
mgr.findPersonsByHappenDate();
HibernateUtil.sessionFactory.close();
}
//第一個查詢方法
private void findPersons()
{
//得到Hibernate Session
Session sess = HibernateUtil.currentSession();
//開始事務
Transaction tx = sess.beginTransaction();
//以HQL語句建立Query對象.
//執行setString方法爲HQL語句的參數賦值
//Query調用list方法訪問查詢的所有實例
List pl = sess.createQuery("from Person p where p.myEvents.title
= :eventTitle")
.setString("eventTitle","很普通事情")
.list();
//遍歷查詢的所有結果
for (Iterator pit = pl.iterator() ; pit.hasNext(); )
{
Person p = ( Person )pit.next();
System.out.println(p.getName());
}
//提交事務
tx.commit();
HibernateUtil.closeSession();
}
//第二個查詢方法
private void findPersonsByHappenDate()throws Exception
{
//得到Hibernate Session對象
Session sess = HibernateUtil.currentSession();
Transaction tx = sess.beginTransaction();
//解析出Date對象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date start = sdf.parse("2005-01-01");
System.out.println("系統開始經過日期查找人" + start);
//經過Session的createQuery方法建立Query對象
//設置參數
//返回結果集
List pl = sess.createQuery(
"from Person p where p.myEvents.happenDate between :firstDate
and :endDate")
.setDate("firstDate",start)
.setDate("endDate",new Date())
.list();
//遍歷結果集
for (Iterator pit = pl.iterator() ; pit.hasNext(); )
{
Person p = ( Person )pit.next();
System.out.println(p.getName());
}
tx.commit();
HibernateUtil.closeSession();
}
}
經過上面的示例程序,可看出查詢步驟基本類似。Query對象能夠連續屢次設置參數,這得益於Hibernate Query的設計。
一般,setXxx方法的返回值都是void,但Hibernate Query的setXxx方法返回值是Query自己。所以,程序經過Session建立Query後,直接屢次調用setXxx方法爲HQL語句的參數賦值,再直接調用list方法返回查詢到的所有結果便可。
Query還包含兩個方法:
● setFirstResult(int firstResult),設置返回的結果集從第幾條記錄開始。
● setMaxResults(int maxResults),設置本次查詢返回的結果數。
這兩個方法用於實現Hibernate分頁。
下面簡單介紹HQL語句的語法。
HQL語句自己是不區分大小寫的。也就是說,HQL語句的關鍵字和函數都是不區分大小寫的。但HQL語句中所使用的包名、類名、實例名和屬性名都區分大小寫。
from子句是最簡單的HQL語句,也是最基本的HQL語句。from關鍵字後緊跟持久化類的類名。例如:
from Person
代表從Person持久化類中選出所有的實例。
大部分時候,推薦爲該Person的每一個實例起別名。例如:
from Person as p
在上面的HQL語句中,Person持久化類中的實例的別名爲p,既然 p是實例名,所以也應該遵照Java的命名規則:第一個單詞的首字母小寫,後面每一個單詞的首字母大寫。
命名別名時,as關鍵字是可選的,但爲了增長可讀性,建議保留。
from後還可同時出現多個持久化類,此時將產生一個笛卡兒積或跨表的鏈接。
select子句用於肯定選擇出的屬性,固然select選擇的屬性必須是from後持久化類包含的屬性。例如:
select p.name from Person as p
select能夠選擇任意屬性,不只能夠選擇持久化類的直接屬性,還能夠選擇組件屬性包含的屬性,例如:
select p.name.firstName from Person as p
select也支持將選擇出的屬性存入一個List對象中,例如:
select new list(p.name , p.address) from Person as p
甚至能夠將選擇出的屬性直接封裝成對象,例如:
select new ClassTest(p.name , p.address) from Person as p
前提是ClassTest支持p.name和p.address的構造器,假如p.name的數據類型是 String,p.address的數據類型是String,則ClassTest必須有以下的構造器:
ClassTest(String s1, String s2)
select還支持給選中的表達式命名別名,例如:
select p.name as personName from Person as p
這種用法與new map結合使用更廣泛。如:
select new map(p.name as personName) from Person as p
在這種情形下,選擇出的是Map結構,以personName爲key,實際選出的值做爲value。
HQL也支持在選出的屬性上,使用匯集函數。HQL支持的彙集函數與SQL徹底相同,有以下5個:
● avg,計算屬性平均值。
● count,統計選擇對象的數量。
● max,統計屬性值的最大值
● min,統計屬性值的最小值。
● sum,計算屬性值的總和。
例如:
select count(*) from Person
select max(p.age) from Person as p
select子句還支持字符串鏈接符、算術運算符以及SQL函數。如:
select p.name || "" || p.address from Person as p
select子句也支持使用distinct和all關鍵字,此時的效果與SQL中的效果徹底相同。
HQL語句被設計成能理解多態查詢,from後跟的持久化類名,不只會查詢出該持久化類的所有實例,還會查詢出該類的子類的所有實例。
以下面的查詢語句:
from Person as p
該查詢語句不只會查詢出Person的所有實例,還會查詢出Person的子類,如Teacher的所有實例,前提是Person和Teacher完成了正確的繼承映射。
HQL支持在from子句中指定任何Java類或接口,查詢會返回繼承了該類的持久化子類的實例或返回實現該接口的持久化類的實例。下面的查詢語句返回全部被持久化的對象:
from java.lang.Object o
若是Named接口有多個持久化類,下面的語句將返回這些持久化類的所有實例:
from Named as n
注意:後面的兩個查詢將須要多個SQL SELECT語句,所以沒法使用order by子句對結果集進行排序,從而,不容許對這些查詢結果使用Query.scroll()方法。
where子句用於篩選選中的結果,縮小選擇的範圍。若是沒有爲持久化實例命名別名,能夠直接使用屬性名引用屬性。
以下面的HQL語句:
from Person where name like 'tom%'
上面HQL語句與下面的語句效果相同:
from Person as p where p.name like "tom%"
在後面的HQL語句中,若是爲持久化實例命名了別名,則應該使用完整的屬性名。兩個HQL語句均可返回name屬性以tom開頭的實例。
複合屬性表達式增強了where子句的功能,例如以下HQL語句:
from Cat cat where cat.mate.name like "kit%"
該查詢將被翻譯成爲一個含有內鏈接的SQL查詢,翻譯後的SQL語句以下:
select * from cat_table as table1 cat_table as table2 where table1.mate =
table2.id and table1.name like "kit%"
再看下面的HQL查詢語句:
from Foo foo where foo.bar.baz.customer.address.city like"guangzhou%"
翻譯成SQL查詢語句,將變成一個四表鏈接的查詢。
=運算符不只能夠被用來比較屬性的值,也能夠用來比較實例:
from Cat cat, Cat rival where cat.mate = rival.mate
select cat, mate
from Cat cat, Cat mate
where cat.mate = mate
特殊屬性(小寫)id能夠用來表示一個對象的標識符。(也能夠使用該對象的屬性名。)
from Cat as cat where cat.id = 123
from Cat as cat where cat.mate.id = 69
第二個查詢是一個內鏈接查詢,但在HQL查詢語句下,無須體會多表鏈接,而徹底使用面向對象方式的查詢。
id也可表明引用標識符。例如,Person類有一個引用標識符,它由country屬性 與medicareNumber兩個屬性組成。
下面的HQL語句有效:
from Person as person
where person.id.country = 'AU'
and person.id.medicareNumber = 123456
from Account as account
where account.owner.id.country = 'AU'
and account.owner.id.medicareNumber = 123456
第二個查詢跨越兩個表Person和Account。是一個多表鏈接查詢,但此處感覺不到多表鏈接查詢的效果。
在進行多態持久化的狀況下,class關鍵字用來存取一個實例的鑑別值(discriminator value)。嵌入where子句中的Java類名,將被做爲該類的鑑別值。例如:
from Cat cat where cat.class = DomesticCat
where子句中的屬性表達式必須以基本類型或java.lang.String結尾,不要使用組件類型屬性結尾,例如Account有Person屬性,而Person有Name屬性,Name有firstName屬性。
看下面的情形:
from Account as a where a.person.name.firstName like "dd%" //正確
from Account as a where a.person.name like "dd%" //錯誤
HQL的功能很是豐富,where子句後支持的運算符異常豐富,不只包括SQL的運算符,還包括EJB-QL的運算符等。
where子句中容許使用大部分SQL支持的表達式:
● 數學運算符+、–、*、/ 等。
● 二進制比較運算符=、>=、<=、<>、!=、like等。
● 邏輯運算符and、or、not等。
● in、not in、between、is null、is not null、is empty、is not empty、member of和not member of等。
● 簡單的case、case ... when ... then ... else ... end和case、case when ... then ... else ... end等。
● 字符串鏈接符value1 || value2或使用字符串鏈接函數concat(value1 , value2)。
● 時間操做函數current_date()、current_time()、current_timestamp()、second()、minute()、hour()、day()、month()、year()等。
● HQL還支持EJB-QL 3.0所支持的函數或操做substring()、trim()、lower()、upper()、length()、locate()、abs()、sqrt()、bit_length()、coalesce()和nullif()等。
● 還支持數據庫的類型轉換函數,如cast(... as ...),第二個參數是Hibernate的類型名,或者extract(... from ...),前提是底層數據庫支持ANSI cast() 和extract()。
● 若是底層數據庫支持以下單行函數sign()、trunc()、rtrim()、sin()。則HQL語句也徹底能夠支持。
● HQL語句支持使用?做爲參數佔位符,這與JDBC的參數佔位符一致,也可以使用命名參數佔位符號,方法是在參數名前加冒號 :,例如 :start_date和:x1等。
● 固然,也可在where子句中使用SQL常量,例如'foo'、6九、'1970-01-01 10:00: 01.0'等。
● 還能夠在HQL語句中使用Java public static final 類型的常量,例如eg.Color.TABBY。
除此以外,where子句還支持以下的特殊關鍵字用法。
● in與between...and可按以下方法使用:
from DomesticCat cat where cat.name between 'A' and 'B'
from DomesticCat cat where cat.name in ( 'Foo','Bar','Baz')
● 固然,也支持not in和not between...and的使用,例如:
from DomesticCat cat where cat.name not between 'A' and 'B'
from DomesticCat cat where cat.name not in ( 'Foo','Bar','Baz' )
● 子句is null與is not null能夠被用來測試空值,例如:
from DomesticCat cat where cat.name is null;
from Person as p where p.address is not null;
若是在Hibernate配置文件中進行以下聲明:
<property name="hibernate.query.substitutions">true 1, false 0</property>
上面的聲明代表,HQL轉換SQL語句時,將使用字符1和0來取代關鍵字true和false。而後將能夠在表達式中使用布爾表達式,例如:
from Cat cat where cat.alive = true
● size關鍵字用於返回一個集合的大小,例如:
from Cat cat where cat.kittens.size > 0
from Cat cat where size(cat.kittens) > 0
● 對於有序集合,還可以使用minindex與maxindex函數表明最小與最大的索引序數。同理,能夠使用minelement與maxelement函數表明集合中最小與最大的元素。 例如:
from Calendar cal where maxelement(cal.holidays) > current date
from Order order where maxindex(order.items) > 100
from Order order where minelement(order.items) > 10000
● 能夠使用SQL函數any、some、all、exists、in操做集合裏的元素,例如:
//操做集合元素
select mother from Cat as mother, Cat as kit
where kit in elements(foo.kittens)
//p的name屬性等於集合中某個元素的name屬性
select p from NameList list, Person p
where p.name = some elements(list.names)
//操做集合元素
from Cat cat where exists elements(cat.kittens)
from Player p where 3 > all elements(p.scores)
from Show show where 'fizard' in indices(show.acts)
注意這些結構變量size、elements、indices、minindex、maxindex、minelement、maxelement 等,只能在where子句中使用。
● where子句中,有序集合的元素(arrays, lists, maps)能夠經過[ ]運算符訪問。例如:
//items是有序集合屬性,items[0]表明第一個元素
from Order order where order.items[0].id = 1234
//holidays是map集合屬性,holidays[national day]表明其中一個元素
select person from Person person, Calendar calendar
where calendar.holidays['national day'] = person.birthDay
and person.nationality.calendar = calendar
//下面同時使用list 集合和map集合屬性
select item from Item item, Order order
where order.items[ order.deliveredItemIndices[0] ] = item and order.id = 11
select item from Item item, Order order
where order.items[ maxindex(order.items) ] = item and order.id = 11
在[]中的表達式甚至能夠是一個算術表達式,例如:
select item from Item item, Order order
where order.items[ size(order.items) - 1 ] = item
藉助於HQL,能夠大大簡化選擇語句的書寫,提升查詢語句的可讀性,看下面的HQL語句:
select cust
from Product prod,
Store store
inner join store.customers cust
where prod.name = 'widget'
and store.location.name in ( 'Melbourne', 'Sydney' )
and prod = all elements(cust.currentOrder.lineItems)
若是翻譯成SQL語句,將變成以下形式:
SELECT cust.name, cust.address, cust.phone, cust.id, cust.current_order
FROM customers cust,
stores store,
locations loc,
store_customers sc,
product prod
WHERE prod.name = 'widget'
AND store.loc_id = loc.id
AND loc.name IN ( 'Melbourne', 'Sydney' )
AND sc.store_id = store.id
AND sc.cust_id = cust.id
AND prod.id = ALL(
SELECT item.prod_id
FROM line_items item, orders o
WHERE item.order_id = o.id
AND cust.current_order = o.id
)
查詢返回的列表(list)能夠根據類或組件屬性的任何屬性進行排序,例如:
from Person as p
order by p.name, p.age
還可以使用asc或desc關鍵字指定升序或降序的排序規則,例如:
from Person as p
order by p.name asc , p.age desc
若是沒有指定排序規則,默認採用升序規則。便是否使用asc關鍵字是沒有區別的,加asc是升序排序,不加asc也是升序排序。
返回彙集值的查詢能夠對持久化類或組件屬性的屬性進行分組,分組所使用的group by子句。看下面的HQL查詢語句:
select cat.color, sum(cat.weight), count(cat)
from Cat cat
group by cat.color
相似於SQL的規則,出如今select後的屬性,要麼出如今彙集函數中,要麼出如今group by的屬性列表中。看下面示例:
//select後出現的id出如今group by以後,而name屬性則出如今彙集函數中
select foo.id, avg(name), max(name)
from Foo foo join foo.names name
group by foo.id
having子句用於對分組進行過濾,以下:
select cat.color, sum(cat.weight), count(cat)
from Cat cat
group by cat.color
having cat.color in (eg.Color.TABBY, eg.Color.BLACK)
注意:having子句用於對分組進行過濾,所以having子句只能在有group by子句時才能夠使用,沒有group by子句,不能使用having子句。
Hibernate的HQL語句會直接翻譯成數據庫SQL語句。所以,若是底層數據庫支持的having子句和group by子句中出現通常函數或彙集函數,HQL語句的having子句和order by 子句中也能夠出現通常函數和彙集函數。
例如:
select cat
from Cat cat
join cat.kittens kitten
group by cat
having avg(kitten.weight) > 100
order by count(kitten) asc, sum(kitten.weight) desc
注意:group by子句與 order by子句中都不能包含算術表達式。
若是底層數據庫支持子查詢,則能夠在HQL語句中使用子查詢。與SQL中子查詢類似的是,HQL中的子查詢也須要使用()括起來。如:
from Cat as fatcat
where fatcat.weight > ( select avg(cat.weight) from DomesticCat cat )
若是select中包含多個屬性,則應該使用元組構造符:
from Cat as cat
where not ( cat.name, cat.color ) in (
select cat.name, cat.color from DomesticCat cat
)
對於集合屬性,Hibernate默認採用延遲加載策略。例如,對於持久化類Person,有集合屬性scores。加載Person實例時,默認不加載scores屬性。若是Session被關閉,Person實例將沒法訪問關聯的scores屬性。
爲了解決該問題,能夠在Hibernate映射文件中取消延遲加載或使用fetch join,例如:
from Person as p join p.scores
上面的fetch語句將會初始化person的scores集合屬性。
若是使用了屬性級別的延遲獲取,能夠使用fetch all properties來強制Hibernate當即抓取那些本來須要延遲加載的屬性,例如:
from Document fetch all properties order by name
from Document doc fetch all properties where lower(doc.name) like '%cats%'
HQL查詢還支持將查詢所用的HQL語句放入配置文件中,而不是代碼中。經過這種方式,能夠大大提供程序的解耦。
使用query元素定義命名查詢,下面是定義命名查詢的配置文件片斷:
<!-- 定義命名查詢 -->
<query name="myNamedQuery">
<!-- 此處肯定命名查詢的HQL語句 -->
from Person as p where p.age > ?
</query>
該命名的HQL查詢能夠直接經過Session訪問,調用命名查詢的示例代碼以下:
private void findByNamedQuery()throws Exception
{
//得到Hibernate Session對象
Session sess = HibernateUtil.currentSession();
//開始事務
Transaction tx = sess.beginTransaction();
System.out.println("執行命名查詢");
//調用命名查詢
List pl = sess.getNamedQuery("myNamedQuery")
//爲參數賦值
.setInteger(0 , 20)
//返回所有結果
.list();
//遍歷結果集
for (Iterator pit = pl.iterator() ; pit.hasNext(); )
{
Person p = ( Person )pit.next();
System.out.println(p.getName());
}
//提交事務
tx.commit();
HibernateUtil.closeSession();
}
posted @ 2009-07-19 08:48 jadmin 閱讀(4) 評論(0) 編輯
Hibernate徹底以面向對象的方式來操做數據庫,當程序裏以面向對象的方式操做持久化對象時,將被自動轉換爲對數據庫的操做。例如調用Session的delete()方法來刪除持久化對象,Hibernate將負責刪除對應的數據記錄;當執行持久化對象的set方法時,Hibernate將自動轉換爲對應的update方法,修改數據庫的對應記錄。
問題是若是須要同時更新100 000條記錄,是否是要逐一加載100 000條記錄,而後依次調用set方法——這樣不只繁瑣,數據訪問的性能也十分糟糕。對這種批量處理的場景,Hibernate提供了批量處理的解決方案,下面分別從批量插入、批量更新和批量刪除3個方面介紹如何面對這種批量處理的情形。
若是須要將100 000條記錄插入數據庫,一般Hibernate可能會採用以下作法:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
for ( int i=0; i<100000; i++ ) {
User u = new User (.....);
session.save(customer);
}
tx.commit();
session.close();
但隨着這個程序的運行,總會在某個時候運行失敗,而且拋出OutOfMemoryException(內存溢出異常)。這是由於Hibernate的Session持有一個必選的一級緩存,全部的User實例都將在Session級別的緩存區進行了緩存的緣故。
爲了解決這個問題,有個很是簡單的思路:定時將Session緩存的數據刷新入數據庫,而不是一直在Session級別緩存。能夠考慮設計一個累加器,每保存一個User實例,累加器增長1。根據累加器的值決定是否須要將Session緩存中的數據刷入數據庫。
下面是增長100 000個User實例的代碼片斷:
private void testUser()throws Exception
{
//打開Session
Session session = HibernateUtil.currentSession();
//開始事務
Transaction tx = session.beginTransaction();
//循環100 000次,插入100 000條記錄
for (int i = 0 ; i < 1000000 ; i++ )
{
//建立User實例
User u1 = new User();
u1.setName("xxxxx" + i);
u1.setAge(i);
u1.setNationality("china");
//在Session級別緩存User實例
session.save(u1);
//每當累加器是20的倍數時,將Session中的數據刷入數據庫,並清空Session緩存
if (i % 20 == 0)
{
session.flush();
session.clear();
tx.commit();
tx = session.beginTransaction();
}
}
//提交事務
tx.commit();
//關閉事務
HibernateUtil.closeSession();
}
上面代碼中,當i%20 == 0時,手動將Session處的緩存數據寫入數據庫,並手動提交事務。若是不提交事務,數據將依然緩存在事務處——未進入數據庫,也將引發內存溢出的異常。
這是對Session級別緩存的處理,還應該經過以下配置來關閉SessionFactory的二級 緩存。
hibernate.cache.use_second_level_cache false
注意:除了要手動清空Session級別的緩存外,最好關閉SessionFactory級別的二級緩存。不然,即便手動清空Session級別的緩存,但由於在SessionFactory級別還有緩存,也可能引起異常。
上面介紹的方法一樣適用於批量更新數據,若是須要返回多行數據,能夠使用scroll()方法,從而可充分利用服務器端遊標所帶來的性能優點。下面是進行批量更新的代碼片斷:
private void testUser()throws Exception
{
//打開Session
Session session = HibernateUtil.currentSession();
//開始事務
Transaction tx = session.beginTransaction();
//查詢出User表中的全部記錄
ScrollableResults users = session.createQuery("from User")
.setCacheMode(CacheMode.IGNORE)
.scroll(ScrollMode.FORWARD_ONLY);
int count=0;
//遍歷User表中的所有記錄
while ( users.next() )
{
User u = (User) users.get(0);
u.setName("新用戶名" + count);
//當count爲20的倍數時,將更新的結果從Session中flush到數據庫
if ( ++count % 20 == 0 )
{
session.flush();
session.clear();
}
}
tx.commit();
HibernateUtil.closeSession();
}
經過這種方式,雖然能夠執行批量更新,但效果很是很差。執行效率不高,並且須要先執行數據查詢,而後再執行數據更新,而且這種更新將是逐行更新,即每更新一行記錄,都須要執行一條update語句,性能很是低下。
爲了不這種狀況,Hibernate提供了一種相似於SQL的批量更新和批量刪除的HQL語法。
Hibernate提供的HQL語句也支持批量的UPDATE和DELETE語法。
批量UPDATE和DELETE語句的語法格式以下:
UPDATE | DELETE FROM? ClassName [WHERE WHERE_CONDITIONS]
關於上面的語法格式有如下四點值得注意:
● 在FROM子句中,FROM關鍵字是可選的。即徹底能夠不寫FROM關鍵字。
● 在FROM子句中只能有一個類名,該類名不能有別名。
● 不能在批量HQL語句中使用鏈接,顯式的或隱式的都不行。但能夠在WHERE子句中使用子查詢。
● 整個WHERE子句是可選的。
假設,須要批量更改User類實例的name屬性,能夠採用以下代碼片斷完成:
private void testUser()throws Exception
{
//打開Session
Session session = HibernateUtil.currentSession();
//開始事務
Transaction tx = session.beginTransaction();
//定義批量更新的HQL語句
String hqlUpdate = "update User set name = :newName";
//執行更新
int updatedEntities = session.createQuery( hqlUpdate )
.setString( "newName", "新名字" )
.executeUpdate();
//提交事務
tx.commit();
HibernateUtil.closeSession();
}
從上面代碼中能夠看出,這種語法很是相似於PreparedStatement的executeUpdate語法。實際上,HQL的這種批量更新就是直接借鑑了SQL語法的UPDATE語句。
注意:使用這種批量更新語法時,一般只須要執行一次SQL的UPDATE語句,就能夠完成全部知足條件記錄的更新。但也可能須要執行多條UPDATE語句,這是由於有繼承映射等特殊狀況,例若有一個Person實例,它有Customer的子類實例。當批量更新Person實例時,也須要更新Customer實例。若是採用joined-subclass或union-subclass映射策略,Person和Customer實例保存在不一樣的表中,所以可能須要多條UPDATE語句。
執行一個HQL DELETE,一樣使用 Query.executeUpdate() 方法,下面是一次刪除上面所有記錄的代碼片斷:
private void testUser()throws Exception
{
//打開Session實例
Session session = HibernateUtil.currentSession();
//開始事務
Transaction tx = session.beginTransaction();
//定義批量刪除的HQL語句
String hqlUpdate = "delete User";
//執行批量刪除
int updatedEntities = session.createQuery( hqlUpdate )
.executeUpdate();
//提交事務
tx.commit();
//關閉Session
HibernateUtil.closeSession();
}
由Query.executeUpdate()方法返回一個整型值,該值是受此操做影響的記錄數量。實際上,Hibernate的底層操做是經過JDBC完成的。所以,若是有批量的UPDATE或DELETE操做被轉換成多條UPDATE或DELETE語句,該方法返回的是最後一條SQL語句影響的記錄行數。