什么是事務?
事務指的是業務上的最小工作單元,這組操作要么全部成功,要么全部失敗!
本地事務四大特征ACID:
原子性(Atomicity):事務是一個原子操作,由一系列動作組成。事務的原子性確保動作要么全部完成,要么完全不起作用。
一致性(Consistency):一旦事務完成(不管成功還是失敗),系統必須確保它所建模的業務處於一致的狀態,而不會是部分完成部分失敗。在現實中的數據不應該被破壞。
隔離性(Isolation):可能有許多事務會同時處理相同的數據,因此每個事務都應該與其他事務隔離開來,防止數據損壞。
持久性(Durability):一旦事務完成,無論發生什么系統錯誤,它的結果都不應該受到影響,這樣就能從任何系統崩潰中恢復過來。通常情況下,事務的結果被寫到持久化存儲器中。
spring事務的API介紹
Spring事務管理高層抽象主要包括3個接口:PlatformTransactionManager(事務管理器)、TransactionDefinition(事務定義信息)、TransactionStatus(事務狀態)。
1、PlatformTransactionManager(事務管理器):
Spring並不直接管理事務,而是提供了多種事務管理器,他們將事務管理的職責委托給Hibernate或者JTA等持久化機制所提供的相關平台框架的事務來實現。
Spring事務管理器的接口是org.springframework.transaction.PlatformTransactionManager,通過這個接口,Spring為各個平台如JTA、JDBC、Hibernate、JPA等都提供了對應的事務管理器,但是具體的實現就是各個平台自己的事情了。此接口的內容如下:
Public interface PlatformTransactionManager()...{ // 由TransactionDefinition得到TransactionStatus對象 TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; // 提交 Void commit(TransactionStatus status) throws TransactionException; // 回滾 Void rollback(TransactionStatus status) throws TransactionException; }
JDBC事務:
如果應用程序中直接使用JDBC來進行持久化,DataSourceTransactionManager會為你處理事務邊界。為了使用DataSourceTransactionManager,你需要使用如下的XML將其裝配到應用程序的上下文定義中:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>
實際上,DataSourceTransactionManager是通過調用java.sql.Connection來管理事務,而后者是通過DataSource獲取到的。通過調用連接的commit()方法來提交事務,同樣,事務失敗則通過調用rollback()方法進行回滾。
Hibernate事務:
如果應用程序的持久化是通過Hibernate實習的,那么你需要使用HibernateTransactionManager。對於Hibernate3,需要在Spring上下文定義中添加如下的<bean>
聲明:
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean>
sessionFactory屬性需要裝配一個Hibernate的session工廠,HibernateTransactionManager的實現細節是它將事務管理的職責委托給org.hibernate.Transaction對象,而后者是從Hibernate Session中獲取到的。當事務成功完成時,HibernateTransactionManager將會調用Transaction對象的commit()方法,反之,將會調用rollback()方法。
Java持久化API事務(JPA):
如果你計划使用JPA的話,那你需要使用Spring的JpaTransactionManager來處理事務。你需要在Spring中這樣配置JpaTransactionManager:
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean>
JpaTransactionManager只需要裝配一個JPA實體管理工廠(javax.persistence.EntityManagerFactory接口的任意實現)。JpaTransactionManager將與由工廠所產生的JPA EntityManager合作來構建事務。
Java JTA事務:
如果你沒有使用以上所述的事務管理,或者是跨越了多個事務管理源(比如兩個或者是多個不同的數據源),你就需要使用JtaTransactionManager:
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"> <property name="transactionManagerName" value="java:/TransactionManager" /> </bean>
JtaTransactionManager將事務管理的責任委托給javax.transaction.UserTransaction和javax.transaction.TransactionManager對象,其中事務成功完成通過UserTransaction.commit()方法提交,事務失敗通過UserTransaction.rollback()方法回滾。
2、TransactionDefinition(事務定義信息):
上面講到的事務管理器接口PlatformTransactionManager通過getTransaction(TransactionDefinition definition)方法來得到事務,這個方法里面的參數是TransactionDefinition類,這個類就定義了一些基本的事務屬性。 而TransactionDefinition接口內容如下:
public interface TransactionDefinition { int getPropagationBehavior(); // 返回事務的傳播行為 int getIsolationLevel(); // 返回事務的隔離級別,事務管理器根據它來控制另外一個事務可以看到本事務內的哪些數據 int getTimeout(); // 返回事務必須在多少秒內完成 boolean isReadOnly(); // 事務是否只讀,事務管理器能夠根據這個返回值進行優化,確保事務是只讀的 }
(1)定義隔離級別(isolation level):隔離級別定義了一個事務可能受其他並發事務影響的程度。
如果不考慮隔離性,會引發如下安全問題:臟讀、不可重復讀、幻讀
臟讀(Dirty reads):一個事物讀取了另一個事物改寫但還未提交的數據,如果這些數據被回滾,則讀到的數據是無效的。
不可重復讀(Nonrepeatable read):不可重復讀發生在一個事務執行相同的查詢兩次或兩次以上,但是每次都得到不同的數據時。這通常是因為另一個並發事務在兩次查詢期間進行了更新。
幻讀(Phantom read):幻讀與不可重復讀類似。它發生在一個事務(T1)讀取了幾行數據,接着另一個並發事務(T2)插入了一些數據時。在隨后的查詢中,第一個事務(T1)就會發現多了一些原本不存在的記錄。
不可重復讀與幻讀的區別
不可重復讀的重點是修改: 同樣的條件, 你讀取過的同一條數據, 再次讀取出來發現值不一樣了 。
幻讀的重點在於新增或者刪除: 同樣的條件, 第1次和第2次讀出來的結果記錄數不一樣。
從總的結果來看, 似乎不可重復讀和幻讀都表現為兩次讀取的結果不一致。但如果你從控制的角度來看, 兩者的區別就比較大。
對於前者, 只需要鎖住滿足條件的記錄;對於后者, 要鎖住滿足條件及其相近的記錄。
隔離級別 | 說明 |
ISOLATION_DEFAULT | 使用后端數據庫默認的隔離級別 |
ISOLATION_READ_UNCOMMITTED | 最低的隔離級別,允許讀取尚未提交的數據變更,可能會導致臟讀、幻讀或不可重復讀 |
ISOLATION_READ_COMMITTED | 允許讀取並發事務已經提交的數據,可以阻止臟讀,但是幻讀或不可重復讀仍有可能發生 |
ISOLATION_REPEATABLE_READ | 對同一字段的多次讀取結果都是一致的,除非數據是被本身事務自己所修改,可以阻止臟讀和不可重復讀,但幻讀仍有可能發生 |
ISOLATION_SERIALIZABLE | 最高的隔離級別,完全服從ACID的隔離級別,確保阻止臟讀、不可重復讀以及幻讀,也是最慢的事務隔離級別,因為它通常是通過完全鎖定事務相關的數據庫表來實現的 |
Mysql數據庫用的是REPEATABLE_READ隔離級別
Oracle數據庫用的是READ_COMMITTED隔離級別
(2)定義事物傳播行為(propagation behavior):
在TransactionDefinition接口中規定了7種類型的事務傳播行為,它們規定了事務方法和事務方法發生嵌套調用時事務如何進行傳播。
事務傳播行為類型 | 說明 |
PROPAGATION_REQUIRED | 如果當前沒有事務,就新建一個事務,如果已經存在一個事務中,加入到這個事務中。這是最常見的選擇。 |
PROPAGATION_SUPPORTS | 支持當前事務,如果當前沒有事務,就以非事務方式執行。 |
PROPAGATION_MANDATORY | 使用當前的事務,如果當前沒有事務,就拋出異常。 |
PROPAGATION_REQUIRES_NEW | 新建事務,如果當前存在事務,把當前事務掛起。 |
PROPAGATION_NOT_SUPPORTED | 以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。 |
PROPAGATION_NEVER | 以非事務方式執行,如果當前存在事務,則拋出異常。 |
PROPAGATION_NESTED | 如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則執行與PROPAGATION_REQUIRED類似的操作。 |
當使用PROPAGATION_NESTED時,底層的數據源必須基於JDBC 3.0,並且實現者需要支持保存點事務機制。
幾種容易引起誤解的組合事務傳播行為
當服務接口方法分別使用表1中不同的事務傳播行為,且這些接口方法又發生相互調用的情況下,大部分組合都是一目了然,容易理解的。但是,也存在一些容易引起誤解的組合事務傳播方式。
下面,我們通過兩個具體的服務接口的組合調用行為來破解這一難點。這兩個服務接口分別是UserService和ForumService, UserSerice有一個addCredits()方法,ForumSerivce#addTopic()方法調用了 UserSerice#addCredits()方法,發生關聯性服務方法的調用:
public class ForumService {
private UserService userService;
public void addTopic(){①調用其它服務接口的方法
//add Topic…
userService.addCredits();②被關聯調用的業務方法
}
}
嵌套調用的事務方法
對Spring事務傳播行為最常見的一個誤解是:當服務接口方法發生嵌套調用時,被調用的服務方法只能聲明為 PROPAGATION_NESTED。這種觀點犯了望文生義的錯誤,誤認為PROPAGATION_NESTED是專為方法嵌套准備的。這種誤解遺害不 淺,執有這種誤解的開發者錯誤地認為:應盡量不讓Service類的業務方法發生相互的調用,Service類只能調用DAO層的DAO類,以避免產生嵌 套事務。
其實,這種顧慮是完全沒有必要的,PROPAGATION_REQUIRED已經清楚地告訴我們:事務的方法會足夠“聰明”地判斷上下文是否已經存在一個事務中,如果已經存在,就加入到這個事務中,否則創建一個新的事務。
依照上面的例子,假設我們將ForumService#addTopic()和UserSerice#addCredits()方法的事務傳播行為都設置為PROPAGATION_REQUIRED,這兩個方法將運行於同一個事務中。
為了清楚地說明這點,可以將Log4J的日志設置為DEBUG級別,以觀察Spring事務管理器內部的運行情況。下面將兩個業務方法都設置為PROPAGATION_REQUIRED,Spring所輸出的日志信息如下:
Using transaction object
[org.springframework.jdbc.datasource.DataSourceTransactionManager$DataSourceTransactionObject@e3849c]
①為ForumService#addTopic()新建一個事務
Creating new transaction with name [com.baobaotao.service.ForumService.addTopic]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
Acquired Connection [org.apache.commons.dbcp.PoolableConnection@dc41c5] for JDBC transaction
Switching JDBC Connection [org.apache.commons.dbcp.PoolableConnection@dc41c5] to manual commit
Bound value [org.springframework.jdbc.datasource.ConnectionHolder@ee1ede] for key [org.apache.commons.dbcp.BasicDataSource@4204] to thread [main]
Initializing transaction synchronization
Getting transaction for [com.baobaotao.service.ForumService.addTopic]
Retrieved value [org.springframework.jdbc.datasource.ConnectionHolder@ee1ede] for key [org.apache.commons.dbcp.BasicDataSource@4204] bound to thread [main]
Using transaction object [org.springframework.jdbc.datasource.DataSourceTransactionManager$DataSourceTransactionObject@8b8a47]
②UserService#addCredits()簡單地加入到已存在的事務中(即①處創建的事務)
Participating in existing transaction
Getting transaction for [com.baobaotao.service.UserService.addCredits]
Completing transaction for [com.baobaotao.service.UserService.addCredits]
Completing transaction for [com.baobaotao.service.ForumService.addTopic]
Triggering beforeCommit synchronization
Triggering beforeCompletion synchronization
Initiating transaction commit
③調用底層Connection#commit()方法提交事務
Committing JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@dc41c5]
Triggering afterCommit synchronization
Triggering afterCompletion synchronization
Clearing transaction synchronization
嵌套事務
將ForumService#addTopic()設置為PROPAGATION_REQUIRED時, UserSerice#addCredits()設置為PROPAGATION_REQUIRED、PROPAGATION_SUPPORTS、 PROPAGATION_MANDATORY時,運行的效果都是一致的(當然,如果單獨調用addCredits()就另當別論了)。
當addTopic()運行在一個事務下(如設置為PROPAGATION_REQUIRED),而addCredits()設置為 PROPAGATION_NESTED時,如果底層數據源支持保存點,Spring將為內部的addCredits()方法產生的一個內嵌的事務。如果 addCredits()對應的內嵌事務執行失敗,事務將回滾到addCredits()方法執行前的點,並不會將整個事務回滾。內嵌事務是內層事務的一 部分,所以只有外層事務提交時,嵌套事務才能一並提交。
嵌套事務不能夠提交,它必須通過外層事務來完成提交的動作,外層事務的回滾也會造成內部事務的回滾。
嵌套事務和新事務
PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED也是容易混淆的兩個傳播行為。
PROPAGATION_REQUIRES_NEW 啟動一個新的、和外層事務無關的“內部”事務。該事務擁有自己的獨立隔離級別和鎖,不依賴於外部事務,獨立地提交和回滾。當內部事務開始執行時,外部事務 將被掛起,內務事務結束時,外部事務才繼續執行。
由此可見, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大區別在於:
PROPAGATION_REQUIRES_NEW 將創建一個全新的事務,它和外層事務沒有任何關系,
而
PROPAGATION_NESTED 將創建一個依賴於外層事務的子事務,當外層事務提交或回滾時,子事務也會連帶提交和回滾。
其它需要注意問題
以下幾個問題值得注意:
1) 當業務方法被設置為PROPAGATION_MANDATORY時,它就不能被非事務的業務方法調用。
如將ForumService#addTopic ()設置為PROPAGATION_MANDATORY,如果展現層的Action直接調用addTopic()方法,將引發一個異常。
正確的情況是: addTopic()方法必須被另一個帶事務的業務方法調用(如ForumService#otherMethod())。
所以 PROPAGATION_MANDATORY的方法一般都是被其它業務方法間接調用的。
2) 當業務方法被設置為PROPAGATION_NEVER時,它將不能被擁有事務的其它業務方法調用。假設UserService#addCredits ()設置為PROPAGATION_NEVER,當ForumService# addTopic()擁有一個事務時,addCredits()方法將拋出異常。所以PROPAGATION_NEVER方法一般是被直接調用的。
3)當方法被設置為PROPAGATION_NOT_SUPPORTED時,外層業務方法的事務會被掛起,當內部方法運行完成后,外層方法的事務重新運行。如果外層方法沒有事務,直接運行,不需要做任何其它的事。
3、TransactionStatus(事務狀態):
上面講到的調用PlatformTransactionManager接口的getTransaction()的方法得到的是TransactionStatus接口的一個實現,這個接口的內容如下:
public interface TransactionStatus{ boolean isNewTransaction(); // 是否是新的事物 boolean hasSavepoint(); // 是否有恢復點 void setRollbackOnly(); // 設置為只回滾 boolean isRollbackOnly(); // 是否為只回滾 boolean isCompleted; // 是否已完成 }
事務的實現方式:
1、編程式事務管理:通過TransactionTemplate手動管理事物,與業務耦合度高,很少使用,略。
(1)在AccountService中使用TransactionTemplate
(2)TransactionTemplate依賴DataSourceTransactionManager
(3)DataSourceTransactionManager依賴DataSource構造
2、聲明式事務管理:Spring的聲明式事物是通過AOP實現的。
(1)基於AspectJ的XML方式(經常使用)
(2)基於注解的方式(經常使用)