PartV.TransactiomManagement
github 地址 https://github.com/yanghj7425/article/blob/master/spring/framework/PartIII.CoreTechnologies.md
發(fā)現(xiàn)一個小問題:github 的目錄在 簡書這里不能用。我也就不改了,小伙伴們可以去我github看哈 : ??
這部分的參考文檔關注的是數(shù)據(jù)訪問、數(shù)據(jù)訪問層和業(yè)務邏輯層或者服務層之間的交互。
Spring 的 綜合事務管理支持覆蓋一些細節(jié),Spring 框架可以接入各種覆蓋了數(shù)據(jù)訪問框架和基數(shù)的支持。
目錄
事務管理
Spring事務管理介紹
對綜合事務管理的支持是使用 Spring 的一個令人激動的理由。Spring 框架多事務管理始終如一的抽象帶來了下面的優(yōu)勢:
- 在訪問不同的事務 API 的時候使用統(tǒng)一的編程模型,如 Java Transaction(JTA)、JBDBC、Hibernate、Java Persistence API(JPA)、和 Java Data Objects (JDO)。
- 支持聲明事務管理。
- 對于編程事務管理與 JTA 等復雜的事物 相比 API 更簡單。
- 與 Spring 的數(shù)據(jù)訪問層抽象極好的集成。
下面的部分描述了 Spring 框架的附加事務和技術。(這章也包括討論最佳實戰(zhàn)、應用服務集成、和解決通用問題)
- Spring 框架的高級事務支持模型描述了你為什么應該使用 Spring 框架的抽象事物替代 EJB 容器的事物管理或者選擇通過特有的 API 驅動一個本地事務,如:hibernate。
- 明晰 Spring 框架事務抽象 輪廓,核心類和抽象怎樣從不同種類的數(shù)據(jù)源配置獲得 DataSource。
- 同步事務資源描述了應用代碼如何確保資源正確的創(chuàng)建、重用和清理。
- 聲明事務管理描述了對于聲明事務管理的支持。
- 編程事務管理 覆蓋支持了編碼(這是明確的編碼)事務管理。
- 綁定事務事件 描述了你應該怎樣在一個事務中應用一個事件。
Spring?框架事務模型的高級特性
傳統(tǒng)的,Java EE 開發(fā)者對事務管理有 2 個選擇:全局事務和本地事務。兩者都有深厚的局限性。全局和本地事務管理在下面兩部分復習。接下來討論 Spring 框架的事務管理是怎樣解決了全局和本地事務模型的局限性。
全局事務
全局事務允許你和多個事務型資源一起工作:典型的關系型數(shù)據(jù)庫和消息隊列。應用服務器通過 JTA 管理全局事務,這樣使用 JTA 的 API 是愚蠢的(部分原因是由于他的異常模式)。因此,JTA 使用的 UserTransaction 一般需要從 JNDI 中獲取,意味著你需要為了使用 JTA 而使用 JNDI。明顯的,使用全局事務會限制應用代碼重用的潛力,正如 JTA 通常是在應用服務器的環(huán)境下使用。
在這之前,通過 EJB CMT (Container Managed Transaction) 使用全局事務時比較喜歡的方式。CMT 是聲明事務管理的一種形式(作為編程事務的一種區(qū)分)。EJB CMT 移除了事務相關的 JDNI 的需求,當然 EJB 本身需要使用 JNDI。它移除了太多根本不需要些的 Java 代碼來控制事務。一個嚴重的缺點是 CMT 依賴 JTA 和應用服務器環(huán)境。所以,它僅僅是可用的如果選擇一個在 EJB 中實現(xiàn)業(yè)務邏輯。EJB 一個不太好的是通常都太大了,以至于不是一個吸引人的選擇,尤其是對于聲明事務管理面臨一個信服的方案時。
本地事務
本地事務時一個資源特性,例如一個事務和 JDBC 鏈接關聯(lián)。本地事務可能更加容易使用,但是有一個嚴重的劣勢:不能跨越多個事務性資源工作。比如:編碼管理的事務使用了 JDBC 鏈接的時候不能和全局的 JTA 事務一起工作。因為應用服務器沒有調用事務管理,它不能幫助確保正確的跨越多個資源(對于大多數(shù)的應用使用單一的事務資源時不值得的)。本地事務的另一個缺陷是編程模型由侵入性攻擊。
Spring的一致編程模型
Spring 解決了全局和本地事務的不利之處。它使在任何應用開發(fā)使用一致的編程模型。你寫一次代碼,它可以從不同的事務管理策略在不同的環(huán)境中受益。Spring 框架提供了聲明事務管理和編程事務管理。大部分的使用者喜歡聲明事務管理,這里也是在大多數(shù)情況下推薦使用的。
用編程事務管理,開發(fā)者和 Spring 框架的事務抽象一起工作,這樣可以在任何事務基礎之運行。首選聲明事務模型,開發(fā)者代表性的寫一點兒或者不用寫代碼關聯(lián)事務管理。因此不依賴 Spring 框架的事務 API 或者任何事務的 API。
為了事務管理你需要一個應用服務器嗎 ?
Spring 框架的事務管理支持改變傳統(tǒng)的規(guī)則當一起企業(yè)級的 Java 應用開發(fā)去要一個應用服務器的時候。
特別的,通過 EJB 你不需要一個應用服務器對于聲明事務管理。實際上,如果你的應用服務器有強大的 JTA 功能,你可以決定 Spring 框架的聲明事務管理在更有利的和更有生產性的編程模型,如 EJB CMT 之后。
通常,如果你的應用需要處理事務跨越多個資源你就需要一個有 JTA 功能的應用服務器,這個對大部分應用都不是必須的。許多高端的應用使用單個的、高度可伸縮的數(shù)據(jù)庫替代。單例的事務管理,如:Atomikes Transactions 和 JOTM 的其它選擇。當然,你可能需要一個應用服務的,比如: JAVA Message Service(JMS)、Java EE COntecot Architectrue( JCA )。
Spring 的框架給你選擇你的應用何時完全加載到你的應用服務器。只能選擇使用 EJB CMT 或 JTA 編碼實現(xiàn)類似于 JDBC 鏈接型的本地事務已經(jīng)是過去式了,并且如果想要代碼運行在全局、連接者管理的事務將要面臨巨大的返工。使用 Spring 框架,只需要統(tǒng)一的 bean 定義在你的配置文件,而不是你的代碼需要修改。
明晰Spring框架事務抽象
Spring 事務抽象的關鍵是:事務策略(transaction strategy)的概念。transaction strategy 被定義在 org.springframework.transaction.PlatformTransactionManager 接口:
public interface PlatformTransactionManager {
TransactionStatus getTransaction(
TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
這是一個主要的服務提供者接口(SPI),但是它在你的應用代碼里面可以通過編程的方式實現(xiàn)。因為 PlatformTransactionManager 是一個接口,必要的時候它很容易被欺騙或者剔除。它沒有依賴 JNDI 這樣的查找策略。PlatformTransactionManager 的實現(xiàn)類就像在 Spring 框架的 IOC 容器中定義其它的對象一樣。這樣的好處是使 Spring 框架事務抽象比較值得,甚至和你使用 JTA 一起工作的時候。事務型的編碼比直接使用 JTA 的方式更容易測試。
與 Spring 的哲學一樣,TransactionException 可以被 PlatformTransactionManager 的任何方法拋出,這是一個非檢查異常(是的,繼承自 java.lang.RuntimeException 類)。事務基礎的故障總是致命的。在極少數(shù)的情況下,應用代碼實際上可以從失敗的事務中恢復過來,應用開發(fā)者仍然可以選擇捕獲并且處理 TransactionException。特別的指出開發(fā)者不是強迫這樣做的。
getTransaction(...) 方法返回一個 TransactionStatus 對象,它需要一個 TransactionDefinition 參數(shù)。返回的 TransactionStatus 可能待代表一個新的狀態(tài),如果匹配的事務在當前的調用棧里面就可以代表一個已經(jīng)存在的事務。后面一種情況的含義是:與 Java EE 事務上下文一樣,一個 TransactionStatus 被分配給一個相關的線程執(zhí)行。
TransactionDefinition 接口定義
- 獨立性 (Isolation): 從其它工作的事務中分離出來的事務的程度。例如, 這個事務是否可以看見其它事務中未提交的寫入數(shù)據(jù)嗎?
- 傳遞性 (Propagation): 典型的,左右在一個事務里面執(zhí)行的代碼作用域都那個事務里面。然而,當事務的上下文已經(jīng)存在時,你可以選擇在事件里面指定執(zhí)行方法的行為。例如,代碼可以繼續(xù)運行在已經(jīng)存在的事物(一般情況下),或者存在的事務被掛起,新事務被創(chuàng)建。Spring 提供了從 EJB CMT 中所有的熟悉的事務傳遞選項。
- 超時時間(Timeout):在時間截至之前這個事務運行多長時間,并且自動回滾通過底層的事務基礎。
- 只讀狀態(tài)(Read-only Status):當你的代碼只是讀取并不會修改數(shù)據(jù)的時候就就使用只讀事務。只讀事務在一些情況下時非常有用的優(yōu)化,例如,當你使用 Hibernate。
這些設置反應了標準事務的概念。如果有必要,參考討論事務獨立水平和其它核心事務概念的資料。理解這些概念是使用 Spring 框架或者任何事物管理解決方案的基礎。
TransactionStatus 接口為編碼控制事務的執(zhí)行和查詢事務狀態(tài)提供了一個簡單的方式。這些概念應該是熟悉的,他們是共同的事務 API:
public interface TransactionStatus extends SavepointManager {
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
void flush();
boolean isCompleted();
}
無論選擇 Spring 的聲明事務還是編程事務,正確的定義 PlatformTransactionManager 是基本的。你通??梢酝ㄟ^依賴注入來定義這個實現(xiàn)。
PlatformTransactionManager 的實現(xiàn)通常需要一些他們工作環(huán)境的知識比如:JDBC、JTA、Hibernate 等等。下面的例子展示了你開業(yè)定義一個本地 PlatformTransactionManager 實現(xiàn)(這個例子和普通的 JDBC 工作)。
定義一個 JDBC 數(shù)據(jù)源
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
相關的 PlatformTransactionManager 的定義將參考 DataSource 的定義。它將像這樣:
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
如果你在是使用 Java EE 容器中使用 JTA,你是用的數(shù)據(jù)源從 JNDI 獲取的,與 Spring 的 JtaTransactionManager 合作。這樣看起來像 JTA JNDI 的查找版本:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee.xsd">
<jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/>
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
<!-- other <bean/> definitions here -->
</beans>
JtaTransactionManager 不需要知道 DataSource,或者其他特殊的數(shù)據(jù)源,因為它是用的容器的全局事務管理機制。
注意:
上面定義的數(shù)據(jù)源 dataSources 使用的是 jee 命名空間<jndi-lookup/>標簽。
你也可以容易的是用 Hibernate 的本地事務,正如下面的例子所示。在這種情況下,你需要定義一個 Hibernate LocalSessionFactoryBean,你的應用代碼將要獲得這個 Hibernate session 實例。
DataSource 的定義和之前展示的 local JDBC 例子非常相似。
注意:
如果數(shù)據(jù)源,通過 JNDI 查找,使用了任何一個非 JTA 事務管理,并由 Java EE 容器管理。那么它將是非事務性的,因為 Spring 框架而不是 Java EE 容器將要管理事務。
txManager bean 是 HibernateTransactionManager 類型的一種情形。同樣的方式 DataSourceTransactionManager 需要參考數(shù)據(jù)源,HibernateTransactionManager 需要參考 SessionFactory。
如果你使用 Hibernate 和 Java EE 容器管理 JTA 事務,那么你因該簡單的和之前的 JDBC 的例子一樣使用 JtaTransactionManager。
注意:
如果你使用 JTA,那么你的事務管理將看起來一樣,盡管你是用的數(shù)據(jù)訪問技術可能是 JDBC、Hibernate JPA 或者其他支持的技術。這是實際上是因為 JTA 的事務是一個全局的事務,它可以支持任何的事務資源。
在這所有的情形下,應用代碼不需要改變。你可以改變事務的管理方式僅僅通過改變配置文件,甚至從本地事務改變到全局事務,反之亦然。
同步資源事務
你現(xiàn)在因該清楚你怎樣創(chuàng)建不同的事務管理,他們與資源之間的聯(lián)系和需要同步到事務(例如: DataSourcesTransactionManager 到一個 JDBC DataSource、Hibernate TransactionManager 到 Hibernate SessionFactory 等等)。這部分描述應用代碼怎樣直接或間接的是用持久化的 API 比如 JDBC、Hibernate 或者 JDO 來確保資源的創(chuàng)建、重用、清理。這部分通過 PlatformTransactionManager 也討論事務同步是怎樣被觸發(fā)的。
高級同步的方法
推薦的方法是使用 Spring 最高級別基于模板的持久化 API 或使用具有事務感知工廠的本地 ORM API 或代理管理本地資源工廠。這些事務敏感解決內部處理資源的創(chuàng)建、重用、清理、選擇資源事務同步和異常映射。因此用戶數(shù)據(jù)訪問代碼沒有這些任務的地址,但是可以只專注于沒有模板的持久化邏輯。一般的,你是用本地的 ORM API或者通過 JdbcTemplate 采用一個 template 接近 JDBC 訪問。這些解決方案的細節(jié)是后面的章節(jié)的內容。
低水平的同步方法
類級別的,比如:DataSourceUtils(對于 JDBC)、EntityManagerFactoryUtils(對于 JPA)、SessionFactory(對于 Hibernate)、PersistenceManagerFactoryUtils(對于 JDO)等等,都是現(xiàn)在已存在的低水平的。當你想要應用代碼直接用本地持久化 API 處理資源,你是用這些類來恰當?shù)拇_保本身已經(jīng)被 Spring 框架管理的實例已經(jīng)獲得。事務被同步,出現(xiàn)在進程里的異常被恰當?shù)挠成涞揭恢碌?API 里。
比如,在是用 JDBC 的情況下,替代傳統(tǒng)的 JDBC 在 DataSource 上調用 getConnection() 的方法。相反,像下面這樣使用 org.springframework.jdbc.datasource.DataSourceUtils 類:
Connection conn = DataSourceUtils.getConnection(dataSource);
如果存在的事務已經(jīng)有一個連接同步(處于連接狀態(tài)),這個實例將被返回。否則,這個方法調用觸發(fā)器創(chuàng)建一個新的連接,這會(隨意的)同步任何已經(jīng)存在的事務,并且使在同一個事務里面對于子序列重用成為可能。已經(jīng)提到的,任何的 SQLException 被包裝進 Spring 框架的 CannotGetJdbcConnectionException,一個 Spring 框架級別的非檢查異常 DataAccessExceptions。這個方法提供給你跟多的信息然后你可以更加容易的從 SQLException 中獲得異常信息,并確保
跨數(shù)據(jù)庫移植的可能,甚至跨不同的存儲技術。
這個方法也工作在沒有 Spring 事務管理的時候(事務同步是可選的),所以你可以使用它無論是否使用了 Spring 的事務管理。
當然,一檔你是用了 Spring 的 JDBC、JPA 或 hibernate 支持,你通常將不會使用 DataSourceUtils 或者其他的幫助類,因為你將更開心的工作通過 Spring 的抽象,而不是直接使用關系型的 API。比如,如果你使用 Spring JdbcTemplate 或 jdbc.object 包是為了你使用 JDBC 的精簡,正確的連接檢索出現(xiàn)在幕后的場景,你將不需要寫任何特殊的代碼。
數(shù)據(jù)源事務代理意識
TransactionAwareDataSourceProxy 類是最低水平的存在。這是一個對目標 DataSource 的代理,它封裝了目標 DataSource 為了添加 Spring 的事務管理。在這方面,和一個由 JavaEE 服務提供的事務性的 JNDI DataSource 非常相似。
使用這個類幾乎從來是不需要的和不滿意的,除非當代碼必須被調用并且通過標準的 JDBC 數(shù)據(jù)源接口來實現(xiàn)。在這樣的情況下,這個代碼是可用的,但是參與 Spring 管理的事務。它更喜歡你寫新的代碼通過上面提到的高水平的抽象。
聲明式事務管理
注意:
大多數(shù) Spring 框架的使用者選擇聲名事務管理。這樣的選擇對代碼的影響最小,今后最符合的觀點就是非侵入性的輕量級容器。
Spring 框架的聲名事務管理和 EJB CMT 很相似你可以指定事務行為下降到每一個獨立的方法。這樣做是可以的,如果需要可以在事務的上下文中調用 setRollbackOnly() 方法。兩種不同類型的事務管理類型:
- 和 EJB CMT 不同的,和 JTA 有關系,Spring 框架的 聲名事務管理可以工作在任何環(huán)境。它可以和 JTA 事務或者使用 JBDC、JPA、Hibernate、JDO 的本地事務一起工作,只需要調整一下配置文件。
- 你可以應用 Spring 框架的聲名事務管理到任何類,不只是 EJB 這樣特指的類。
- Spring 框架提供了聲名式回滾規(guī)則,這是 EJB 沒有的。在編程和聲名式的支持中都提供了回滾規(guī)則。
- Spring 框架允許你定制通過 AOP 定制事務的行為。比如:你可以在事務回滾之后插入一個定制的行為。你也可以添加任意的 advice,還有事務 advice。EJB CMT,你不能用 setRollbackOnly() 來影響容器的事務管理。
- 正如做高端應用服務器,Spring 框架不支持遠程調用時的事務傳播。如果你需要這個特性,推薦你使用 EJB。然而,使用這個特性之前請仔細考慮,因為通常我們不想要事務跨過遠程調用。
TransactionProxyFactoryBean ?
聲名事務配置在 Spring2.0 及以上的版本大不同與之前版本的 Spring。主要的區(qū)別是不再需要配置 TransactionProxyFactoryBean。
Spring2.0 之前的配置風格仍然 100% 有效,考慮新的<tx:tags/>為你簡單的定義 TransactionProxyFactoryBean。
回滾規(guī)則的概念是重要的:他們使你指定哪一種 exception(和 throwables)應該觸發(fā)自動回滾。你可以在配置文件里面指定這些聲明而不是在 java 代碼里面。所以雖然你仍然可以在 TransactionStatus 上調用 setRollbackOnly() 方法來回滾當前事物,大多數(shù)的時候你可以指定一個 MyApplicationException 這樣必須回滾的規(guī)則。這樣選擇的的優(yōu)勢是:業(yè)務對象不應該添加到基礎事務結構里面。比如,比較有代表性的是不需要導入 Spring 事務 API 或 Spring 其他API。
雖然 EJB 容器默認在一個系統(tǒng)異常(通常是一個運行時異常)自動回滾事務,EJB CMT 不會在一個應用的異常(一個 被檢查異常而不是 java.rmi.RemoteException)。但是 Spring 對聲明事務管理默認遵守 EJB 的習俗(只有在非檢查異常時才會自動回滾),這個特性對定制一個行為非常有用。
了解Spring框架聲明事務的實現(xiàn)
不要滿足于告訴你簡單的在你的 classes 上使用 @Transactional 注解,添加一個 @EnableTransactionManagement 到你的配置中,然后你希望明白它整體是怎么工作的。這部分將要解釋 Spring 框架如果發(fā)生于事務有關系的事件時聲明事務內部的工作問題。
要掌握 Spring 框架聲明事務支持最重要的概念是通過 AOP 代理啟用,然后事務的 advice 是通過 元數(shù)據(jù)(當前的 XML 或者 注解)驅動的。AOP 和事務元數(shù)據(jù)的整合產生了一個 AOP 代理,使用 TransactionInterceptor 和一個合適的 PlatformTransactionManager 實現(xiàn)類鏈接來驅動事務圍繞方法調用。
[聲明式事務實現(xiàn)的列子]
考慮下面的接口,它是一個綁定的實現(xiàn)。這個例子使用 Foo 和 Bar 類作為占位符為了你可以關注與事務的使用沒有關注在個別的 domain 模型。這個例子的目的,實際上 DefaultFooServive 類在每一個實現(xiàn)體的方法的每一個方法拋出一個 UnSupportOperationException 實例是非常好的。它允許你在響應一個 UnsupportedOperationException 時查看事務的創(chuàng)建和回滾。
// the service interface that we want to make transactional
package x.y.service;
public interface FooService {
Foo getFoo(String fooName);
Foo getFoo(String fooName, String barName);
void insertFoo(Foo foo);
void updateFoo(Foo foo);
}
// an implementation of the above interface
package x.y.service;
public class DefaultFooService implements FooService {
public Foo getFoo(String fooName) {
throw new UnsupportedOperationException();
}
public Foo getFoo(String fooName, String barName) {
throw new UnsupportedOperationException();
}
public void insertFoo(Foo foo) {
throw new UnsupportedOperationException();
}
public void updateFoo(Foo foo) {
throw new UnsupportedOperationException();
}
}
假設 FooService 接口開始的兩個方法,getFoo(String) 和 getFoo(String, String), 必須以 read-only 的語義執(zhí)行事務上下文;其他的方法 insertFoo(Foo) 和 updateFoo(Foo),必須以 read-write 的語義執(zhí)行事務。
<!-- from the file 'context.xml' -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:aspectj-autoproxy />
<!-- this is the service object that we want to make transactional -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<!-- the transactional advice (what 'happens'; see the <aop:advisor/> bean below) -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- the transactional semantics... -->
<tx:attributes>
<!-- all methods starting with 'get' are read-only -->
<tx:method name="get*" read-only="true"/>
<!-- other methods use the default transaction settings (see below) -->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- ensure that the above transactional advice runs for any execution
of an operation defined by the FooService interface -->
<aop:config>
<aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
</aop:config>
<!-- don't forget the DataSource -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
<property name="username" value="scott"/>
<property name="password" value="tiger"/>
</bean>
<!-- similarly, don't forget the PlatformTransactionManager -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- other <bean/> definitions here -->
</beans>
在配置之前檢查。你想要創(chuàng)建一個服務對象,事務性的 fooService bean。事務的語義被應用在封閉的 <tx:advice/> 定義種。<tx:advice/> 定義讀作“…… 左右以 get 開頭的方法執(zhí)行的時候都默認只讀的事務,所有其他的方法執(zhí)行的時候默認擁有的事務配置(read-write)” 。<tx:adivice/> 標簽的 transaction-manager 屬性是設置 PlatformTransactionManager 的名稱,這將驅動事務,在這個例子中是 txManager bean。
提示:
如果你想要有聯(lián)系的 PlatformTransactionManager 的 name 是 transactionManager 你可以在一個事務 advice (<tx:advice/>) 中混合 transaction-mananger 屬性。如果不是,你必須使用 transaction-manager 屬性像之前的例子一樣顯示指定。
<aop:config/> 的定義確保了 txAdvice 定義的 bean 在程序中合適的點被執(zhí)行(fooServiceOperation)。首先,你定義一個切點匹配定義在 FooService 接口中的操作。然后,你用 advisor 分配 txAdivce 切點。結果表明在執(zhí)行 fooServiceOperation 的時候,被 txAdvice 定義的 advice 將要被執(zhí)行。
用 <aop:pointcut/> 元素定義一個 AspectJ 切點表達式。詳情參看 11 章。
通常事務化整個 service 層是必要的。這樣做最簡單的方式是改變切點表達式去匹配你 Service 層的任何操作。比如:
<aop:config>
<aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
</aop:config>
現(xiàn)在我們來分析配置文件,你也許也這樣問你自己:“這些配置文件實際上做了什么?”
上面的配置文件將從 fooService 的定義創(chuàng)建事務代理對象。代理將要被配置為事務性的 advice,為了當一個合適的方法在代理上被調用的時候,事務的啟動、掛起、標記為只讀等等,依賴于事務的配置將和方法關聯(lián)。考慮下面的程序來測試驅動上面的配置。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:context.xml"})
@EnableAspectJAutoProxy
public class AppTest {
@Autowired
private FooService fooService;
@Test
public void test(){
if(fooService != null){
fooService.insertFoo(new Foo());
}
}
}
回滾一個聲名式事務
前面的部分指明了怎樣在你的應用中指定一個事務、典型的 Service 層。這部分展示給你怎樣用一種簡單的方式展現(xiàn)事務的回滾。
關于事務的回滾,再通知節(jié)點 <tx:advice/> 和屬性節(jié)點 <tx:attributes/> 內部通過子節(jié)點 <tx:method/> 設置。
-
<tx:method/>節(jié)點可以設置以下屬性:- name:標識響應事務的方法。eg: get* ,表示以 get 開頭的方法都響應。
- read-only:事務是否為只讀,默認為 false
- propagation:傳遞性
- isolation:獨立性
- no-rollback-for:對于某種異常不回滾
- rollback-for:指定某種異常發(fā)生時,回滾
- timeout:超時時間
Spring 框架默認對所有非檢查異常(RuntimeException)執(zhí)行回滾操作,對所有檢查異常(Exception)不執(zhí)行回滾操作。
關于異常的回滾,也可以在 try-catch 子句處理。
public void transactionRollBack() {
Assert.notNull(fooService, "fooService can`t be null");
try{
fooService.insertFoo(new Foo());
}catch(UnsupportedOperationException e){
TransactionAspectSupport.currentTransactionStatus().isRollbackOnly();
}
}
在大部分情況下,我們的 Service 層可能不會在同一個包中,而且可能需要相同的方法響應不同的異常類型,這時候就可以在 <aop:config/> 節(jié)點內定義多個切點 <aop:pointcut/> 和通知 <aop:advisor/>,來映射到不同的 <tx:advice/> 以滿足需求。
使用@Transactional注解
在有很多個 Service 的情況下可以使用 @Transactional 注解和注解驅動配置來簡化配置文件(<aop:config> 配置)。
<tx:annotation-driven transaction-manager="txManager"/>
如果 PlatformTransactionManager 的 bean id 為 transactionManager,transaction-manager 屬性就可以省略,否則需要顯示的指定 transaction-manager 的值。
如果使用 javaConfig 的方法,只需要在 @Configuration 注解的類上簡單的添加 @EnableTransactionManagement 注解。
-
<tx:annotation-driven/>節(jié)點可以設置一下屬性- transaction-manager : 上面介紹過了
- proxy-target-class: 是否用類代理,默認 false
- mode:代理模式,默認 proxy
@Transactional 可以用在接口和方法上,或者 public 方法上。如果用在非 public 方法上,那要使用 基于 aspectj 的代理模式(織入)。
在方法上使用的注解,會覆蓋在類或者接口上已經(jīng)配置過的注解信息。如:事務只讀狀態(tài)、傳遞性等。
編程式事務
編程事務,文檔上說的比較少。可以參考其他博客。
介紹的就不說了,先看代碼(其實也是官網(wǎng)的代碼,我只是自己跑了一遍)
public class SimpleService {
private final TransactionTemplate transactionTemplate;
private static final Logger logger = LoggerFactory.getLogger(SimpleService.class);
public SimpleService(PlatformTransactionManager transactionManager) {
Assert.notNull(transactionManager, "transactionManager can`t be null");
this.transactionTemplate = new TransactionTemplate(transactionManager);
}
public Object someServiceMethod() {
return transactionTemplate.execute(new TransactionCallback<Object>() {
public Object doInTransaction(TransactionStatus status) {
updateOperation1();
return resultOfUpdateOperation2();
}
});
}
private Object resultOfUpdateOperation2() {
logger.debug(getClass().getSimpleName() + " resultOfUpdateOperation2");
return "resultOfUpdateOperation2";
}
private void updateOperation1() {
logger.debug(getClass().getSimpleName() + " updateOperation1");
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:context.xml" })
public class FooServiceTest {
private SimpleService simpleService;
@Test
public void programmaticTransactionTest() {
simpleService.someServiceMethod();
}
}
添加配置文件:
<bean id="simpleService" class="com.yhj.service.impl.SimpleService">
<constructor-arg name="transactionManager" ref="txManager"></constructor-arg>
<!-- <property name="transactionTemplate" ref="txManager"></property> -->
</bean>
這里注入的時候一直給我一個 NPE,真是那句話:什么也不管丟給你一堆 NPE。
哎,今天第二次打這部分。第一次打完 mv 寫成 rm 了,好痛苦……