spring3.x第十章 Spring的事務管理難點剖析
10.1 DAO和事務管理的牽絆
10.1.1 JDBC訪問資料庫
很多複雜的事物要分步進行,但它們組成一個整體,要麼整體生效,要麼整體失效。這種思想反映到資料庫上,就是多個SQL語句,要麼所有執行成功,要麼所有執行失敗。
資料庫事務有嚴格的定義,它必須同時滿足4個特性,原子性(Atomic)、一致性(Consistency)、隔離性(Isolation)和永續性(Durabiliy)
在jdbcWithoutTx.xml中沒有配置任何事務管理器,但是資料已經持久到資料庫中。預設dataSource資料來源的autoCommit被設定為true,這意味著所有通過JdbcTemplate執行的語句馬上提交,沒有事務。如果設定為false,再次執行將會出錯,原因是新增及更改資料的操作都沒有提交到資料庫。
對於強調讀取速度的應用,資料庫本身可能就不支援事務;如使用MyISAM引擎的MySQL資料庫。即使配置事務管理器也沒有實際用處。
10.1.2 Hibernate訪問資料庫
對於Hibernate來說,情況就有點複雜了。因為Hibernate的事務管理器擁有自身的意義,它和Hibernate一級快取存在密切的關係:當我們呼叫Session的save、update等方法時,Hibernate並不直接向資料庫傳送SQL語句,只在提交事務(commit)或flush一級快取時猜真正向資料庫傳送SQL。所以,即使底層資料庫不支援事務,Hibernate的事務管理也是有一定好處的,不會對資料操作的效率造成負面影響。所以,如果是使用Hibernate資料訪問技術,沒有理由不配置HibernateTransactionManager事務管理。
執行正確執行。這說明Hibernate在Spring中,在沒有事務管理器的情況下,依然可以正常地進行資料的訪問。
10.2 應用分層的迷惑
筆者還是認為需要分層。
10.3 事務方法巢狀呼叫的迷茫
10.3.1 Spring事務傳播機制回顧
Spring事務一個被訛傳很廣說法是:一個事務方法不應該呼叫另一個事務方法,否則將產生兩個事務。結果造成開發人員在設計事務方法時束手束腳。
Spring對事務控制的支援統一在TransactionDefinition類中描述,該類有以下幾個重要的介面方法:
int getPropagationBehavior(): 事務的傳播行為
int getIsolationLevel(): 事務的隔離級別
int getTimeout(): 事務的過期時間
boolean isReadOnly(): 事務的讀寫特性
除了事務的傳播行為外,事務的其他特性Spring是藉助底層資源的功能來完成的,Spring無非只充當個代理的角色。但是事務的傳播行為卻是Spring憑藉自身的框架提供的功能。
所謂是事務傳播行為就是多個事務方法相互呼叫時,事務如何在這些方法間傳播。Spring支援以下7種事務傳播行為。預設為:PROPAGATION_REQUIRED:如果當前沒有事務,就新建一個事務,如果已經存在一個事務,就加入到這個事務種。這是最常見的選擇。
10.3.2 相互巢狀的服務方法
10.4 多執行緒的困惑
10.4.1 Spring通過單例項化Bean簡化多執行緒問題
由於Spring的事務管理器是通過執行緒相關的ThreadLocal來儲存資料訪問基礎設施(也即Connection例項),再結合IOC和AOP實現高階宣告式事務的功能。
Web容器本身就是多執行緒的,Web容器為一個HTTP請求建立一個獨立的執行緒(實際上大多數Web容器採用共享執行緒池),所以由此請求涉及到的Spring容器中Bean也是運行於多執行緒的環境下。
在大多數情況下,Spring的Bean都是單例項的(singleton),單例項Bean的最大好處是執行緒無關性,不存在多執行緒併發訪問的問題。
一個類能夠以單例項的方式執行的前提是”無狀態”:即一個類不能擁有渣u那個太滑的成員變數。DAO必須持有一個Connection,而Connection即是狀態化的物件。所以傳統的DAO不能做成單例項的,每次要用時都還必須建立一個新的例項。傳統的Service由於內部包含了若干個有狀態的DAO成員變數,所以其本身也是有狀態的。
但是在Spring中,DAO和Service都以單例項的方式存在。Spring是通過ThreadLocal將有狀態的變數(如Connection等)本地執行緒化,MAP儲存,達到另一個層面上的”執行緒無關”,使用空間換取時間的方式,從而實現執行緒安全。
由於Spring已經通過THreadLocal的設施將Bean無狀態化,所以單例項的Service可以成功運行於多執行緒環境中,Service本身還可以自由地啟動獨立執行緒以執行其他的Service。
10.4.2 啟動獨立執行緒呼叫事務方法
相同執行緒中進行相互巢狀呼叫的事務方法工作於相同的事務中。如果這些相互巢狀呼叫的方法工作在不同的執行緒中,則不同執行緒下的事務方法工作在獨立的事務中。
10.5 聯合軍種作戰的混亂
10.5.1 Spring事務管理器的應對
如果你採用了一個高階ORM技術(Hibernate),同時採用一個JDBC技術(Spring JDBC)由於前者的會話(Session)是對後者連線(Connection)的封裝,Spring會”足夠智慧地”在同意事務執行緒讓前者的會話封裝後者的連線。所以我們只要直接採用前者的事務管理器就可以了。
10.5.2 Hibernate+Spring JDBC混合框架的事務管理
顯式呼叫了flush()方法,將Session中的快取同步到資料庫中。原因是預設情況下,Hibernate對資料的更改只是記錄一級快取中,要等到事務提交或顯式呼叫flush()方法時才會將一級快取中的資料同步到資料庫中,而提交事務的操作發生在login()方法返回前。
Spring JDBC無法自動感知Hibernate一級快取,所以如果不及時呼叫flush()方法將記錄資料更改的一級快取同步到資料庫中,則通過Spring JDBC進行資料更改的結果將被Hibernate一級快取中的更改覆蓋掉,因為Hibernate一級快取要等到login()方法返回前才同步到資料庫!
使用事務管理器,Hibernate和JDBC使用了資料來源同一個連線,但是,事務同步而快取不同步。最好用Hibernate進行讀寫操作,而只用Spring JDBC進行讀操作。
10.6 特殊方法成漏網之魚
10.6.1 哪些方法不能實施Spring AOP事務
由於Spring事務管理是基於介面代理或動態位元組碼技術,通過AOP實施事務增強的。
對於基於介面動態代理的AOP事務增強來說,由於介面的方法都必然是public的,這就要求實現類的實現方法也必須是public的(不能是protected、private等),同時不能使用static的修飾符。所以,可以實施介面動態代理的方法只能是使用”public”或”public final”修飾符的方法,其他方法不可能被動態代理,相應的也就不能實施AOP增強,即不能進行Spring事務增強。
基於CGLib位元組碼動態代理的方案是通過擴充套件被增強類,動態建立其自雷的方式進行AOP增強植入的。由於使用final、static、private修飾符的方法都不能被子類覆蓋,相應的,這些方法將無法實施AOP增強。
10.6.2 事務增強遺漏例項
10.7 資料連線洩漏
10.7.1 底層連線資源的訪問問題
Spring DAO對所有支援的資料訪問技術框架都使用模板化技術進行了薄層的封裝。只要你的程式都使用Spring DAO的模板(如JdbcTemplate、HibernateTemplate等)進行資料訪問,一定不會存在資料連線洩漏的問題。我們無需關注資料連線(Connection)及其衍生品(Hibernate的Session等)的獲取和釋放操作,模板類已經通過其內部流程替我們完成了,且對開發者是透明的。
但是由於整合第三方產品、整合遺產程式碼等原因,可能需要直接訪問資料來源或直接獲取資料連線及其衍生品。這時,如果使用不當,就可能在無意中創造出一個魔鬼般的連線洩漏問題。
我們知道:當Spring事務方法執行時,就產生一個事務上下文,該上下文在本事務執行執行緒中針對同一個資料來源綁定了一個唯一的資料連線(或其衍生品),所有被該事務上下文傳播的方法都共享了這個資料連線。這個資料連線從資料來源獲取及返回給資料來源都在Spring掌控之中,不會發生問題。如果在需要資料連線時,能夠獲取這個被Spring管控的資料連線,則使用者可以放心使用,無須關注連線釋放的問題。
如何獲取這些被Spring管控的資料連線呢?Spring提供了兩種方法:其一是使用資料資源獲取工具類;其二是對資料來源(或其衍生品如Hibernate的SessionFactory)進行代理。
10.7.2 Spring JDBC資料連線洩漏
獲取Connection連線,沒有顯式的釋放該連線。
10.7.3 通過DataSourceUtils獲取資料鏈接
10.7.5 JdbcTemplate如何做到對連線洩漏的免疫
JdbcTemplate#execute()方法內部,首先都使用DataSourceUtils獲取連線,在方法返回之前使用DataSourceUtils釋放連線。
10.7.6 使用TransactionAwareDataSourceProxy
10.7.7 其他資料訪問技術的等價類
10.8 小結
Spring宣告式事務是Spring最核心、最常用的功能。由於Spring通過IOC和AOP的功能非常透明地實現了宣告式事務的功能,需要懂得如何配置。
在沒有事務管理的情況下,DAO照樣可以順利進行資料操作;
Spring通過事務傳播機制可以很好地應對事務方法巢狀呼叫的情況。
由於單例項的物件不存線上程安全問題,所以經過事務管理增強的單例項Bean可以很好地工作在多執行緒環境下;
混合使用多個數據訪問技術框架需要考慮ORM快取同步的問題。
Spring AOP增強有兩個方案:其一是基於介面的動態代理,其二是基於CGLib動態生成自雷的代理。由於Java語法的特性,有些特殊方法不能被Spring AOP代理,無法享受AOP織入帶來的事務增強;
使用Spring JDBC時如果直接獲取Connection,可能會造成連線洩漏。為降低連線洩漏的可能性,儘量使用DataSourceUtils獲取資料連線。也可以對資料來源進行代理。