說起事務,大家應該多多少少用過,尤其是在一個service方法中調用多次dao操作,我們一定要用到事務( @Transational注解
),那麼這個事務的預設隔離級别和傳播機制是什麼呢?
先來講講
髒讀
不可重複讀
和
幻讀
。
- 髒讀:我們在并發程式設計中是很熟悉的,通俗的講就是你讀得資料已經被修改了,已經過時失去意義了。
- 不可重複讀: 同一個事務裡面多次讀取同一行資料,卻傳回不同的結果。
- 幻讀:同樣一筆查詢在整個事務過程中多次執行後,查詢所得的結果集不一樣。
事務四大特性 ACID
1. 原子性
(Atomicity)
要求事務所包含的全部操作是一個不可分割的整體,如果有一步發生異常,則全部不送出。
2. 一緻性
(Consistency)
A給B轉錢,A減和B增這兩個操作必須保持一緻。
3. 隔離性
(Isolation)
事務會将一部分資料與其他事務隔離,防止髒讀等。
4. 持久性
(Durability)
事務的結果被寫到持久化存儲器中。
事務四大隔離級别
隔離級别越高,則性能相對越低,反之亦然。
1. Read Uncommitted
最低的隔離級别,跟你直譯的意思一樣:可以讀取其它事務未完成的結果。(髒讀)
很明顯,
髒讀
不可重複讀
和
幻讀
這三個問題它都有。
2. Read Committed
大部分資料庫采用的預設隔離級别,比上一個隔離級别多了限定:在該事務完成後,才能讀取該事務的資料更新後的結果。
它可以避免髒讀,但是也有不可重複讀取和幻讀的問題。
3. Repeatable Read
可以保證在整個事務的過程中,對同一筆資料的讀取結果是相同的,不管其他事務是否同時在對同一筆資料進行更新,也不管其他事務對同一筆數 據的更新送出與否。
Repeatable Read隔離級别避免了髒讀和不可重複讀取的問題,但無法避免幻讀。
4. Serializable
最為嚴格的隔離級别,所有的事務操作都必須依次順序執行,可以避免其他隔離級别遇到的所有問題,是最為安全的隔離級别, 但同時也是性能最差的隔離級别。
通常情況下,我們會使用其他隔離級别加上相應的并發鎖的機制來控制對資料的通路,這樣既保證 了系統性能不會損失太大,也能夠一定程度上保證資料的一緻性。
Spring事務傳播機制
從JDBC的事務說起
我們都知道,JDBC給我們提供了事務。
-
try{
-
con.setAutoCommit(false);//開啟事務
-
......
-
con.commit();//try的最後送出事務
-
} catch() {
-
con.rollback();//復原事務
-
}
擷取事務隔離級别
-
Connection.getTransactionIsolation()
設定事務隔離級别
-
con.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
Spring事務機制
Spring并不會直接管理事務,而是提供了事務管理器,将事務管理的職責委托給JPA JDBC JTA DataSourceTransaction JMSTransactionManager 等架構提供的事務來實作。
那麼,Spring提供的事務管理器是什麼呢?
是
PlatformTransactionManager.java
接口:
PlatformTransactionManager.java
Spring提供的事務管理器。不同的事務遵循該事務管理器的API,便能很輕松的交給Spring管理。
-
public interface PlatformTransactionManager {
-
// 通過Transation定義 擷取Transation
-
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
-
// 送出事務
-
void commit(TransactionStatus var1) throws TransactionException;
-
// 復原事務
-
void rollback(TransactionStatus var1) throws TransactionException;
-
}
可以看到它裡面引用到了
TransactionDefinition
和
TransactionStatus
.
TransactionDefinition.java
它裡面包含了事務的定義。
-
public interface TransactionDefinition {
-
// 傳播機制
-
int PROPAGATION_REQUIRED = 0;
-
int PROPAGATION_SUPPORTS = 1;
-
int PROPAGATION_MANDATORY = 2;
-
int PROPAGATION_REQUIRES_NEW = 3;
-
int PROPAGATION_NOT_SUPPORTED = 4;
-
int PROPAGATION_NEVER = 5;
-
int PROPAGATION_NESTED = 6;
-
// 隔離級别
-
int ISOLATION_DEFAULT = -1;
-
int ISOLATION_READ_UNCOMMITTED = 1;
-
int ISOLATION_READ_COMMITTED = 2;
-
int ISOLATION_REPEATABLE_READ = 4;
-
int ISOLATION_SERIALIZABLE = 8;
-
int TIMEOUT_DEFAULT = -1;
-
int getPropagationBehavior();
-
// 擷取隔離級别
-
int getIsolationLevel();
-
int getTimeout();
-
boolean isReadOnly();
-
@Nullable
-
String getName();
-
}
TransactionStatus.java
事務的狀态。
-
public interface TransactionStatus extends SavepointManager, Flushable {
-
boolean isNewTransaction();
-
boolean hasSavepoint();
-
void setRollbackOnly();
-
boolean isRollbackOnly();
-
void flush();
-
boolean isCompleted();
-
}
Spring預設事務使用
1. 代碼方式使用
-
@Autowired
-
private PlatformTransactionManager transactionManager;
-
public void testTX(){
-
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
-
TransactionStatus status = transactionManager.getTransaction(definition);
-
try {
-
// 業務邏輯
-
// ...
-
// 送出事務
-
transactionManager.commit(status);
-
}catch (Exception e){
-
// 發生異常,事務復原
-
transactionManager.rollback(status);
-
}
-
}
2. 注解方式使用
-
@Transactional
-
void testTX2(){
-
// 業務邏輯 ...
-
}
這不是玄學,它的底層是依靠AOP動态代理實作,其實重新渲染出的代碼和第一個使用方式類似,不過大大減少了開發複雜度。
擴充:@Transactional注解
-
@Target({ElementType.METHOD, ElementType.TYPE})
-
@Retention(RetentionPolicy.RUNTIME)
-
@Inherited
-
@Documented
-
public @interface Transactional {
-
//指定使用的事務管理器
-
@AliasFor("transactionManager")
-
String value() default "";
-
@AliasFor("value")
-
String transactionManager() default "";
-
// 可選的事務傳播行為設定
-
Propagation propagation() default Propagation.REQUIRED;
-
// 可選的事務隔離級别設定
-
Isolation isolation() default Isolation.DEFAULT;
-
// 事務逾時時間設定
-
int timeout() default -1;
-
// 讀寫或隻讀事務,預設讀寫
-
boolean readOnly() default false;
-
// 導緻事務復原的異常類數組
-
Class<? extends Throwable>[] rollbackFor() default {};
-
// 導緻事務復原的異常類名字數組
-
String[] rollbackForClassName() default {};
-
// 不會導緻事務復原的異常類數組
-
Class<? extends Throwable>[] noRollbackFor() default {};
-
// 不會導緻事務復原的異常類名字數組
-
String[] noRollbackForClassName() default {};
-
}
Spring事務實踐
非入門選手下面的demo可能會引起你的不适(浪費時間)。 假設我要完成一個功能,當删除使用者的時候,将與該使用者有關的所有資料行都删除。
-
public void delUser(Integer userId) {
-
// 删除和使用者相關的資訊
-
otherRepository.deleteByUserId(userId);
-
// 删除使用者
-
userRepository.deleteById(userId);
-
}
這樣的寫法一般來講,會成功的完成任務。但是如果這樣一段代碼:
-
public void delUser(Integer userId) {
-
// 删除和使用者相關的資訊
-
otherRepository.deleteByUserId();
-
if (true) {
-
throw new RuntimeException("xxx");
-
}
-
// 删除使用者
-
userRepository.deleteById(userId);
-
}
結果會是:
deleteByUserId()
執行成功,
deleteById()
執行失敗,不滿足資料的一緻性。
是以我們需要事務來限制:要麼全部執行,要麼全部不執行(方法中有異常就自動復原)。那怎麼實作呢,隻需要在方法上加一個注解:
@Transactional
-
@Transactional
-
public void delUser(Integer userId) {
-
// 删除和使用者相關的資訊
-
otherRepository.deleteByUserId();
-
if (true) {
-
throw new RuntimeException("xxx");
-
}
-
// 删除使用者
-
userRepository.deleteById(userId);
-
}
Spring 加載第三方事務管理
比如我有個需求(接着上次的強票系統II),要求資訊不能丢失,要用到RabbitMQ的事務管理,那怎麼去加載到Spring的事務管理器中呢?
-
@Bean
-
public ConnectionFactory connectionFactory() {
-
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
-
return connectionFactory;
-
}
-
@Bean
-
public RabbitTransactionManager rabbitTransactionManager(ConnectionFactory connectionFactory) {
-
return new RabbitTransactionManager(connectionFactory);
-
}
我們隻需要這樣做便可以使的使用
@Transactional注解
來實作對RabbitMQ的事務管理,其它架構也類似。
@Transactional注解
概述
@Transactional注解
事務管理對于企業應用來說是至關重要的,即使出現異常情況,它也可以保證資料的一緻性。
Spring Framework對事務管理提供了一緻的抽象,其特點如下:
- 為不同的事務API提供一緻的程式設計模型,比如JTA(Java Transaction API), JDBC, Hibernate, JPA(Java Persistence API和JDO(Java Data Objects)
- 支援聲明式事務管理,特别是基于注解的聲明式事務管理,簡單易用
- 提供比其他事務API如JTA更簡單的程式設計式事務管理API
- 與spring資料通路抽象的完美內建
事務管理方式
spring支援程式設計式事務管理和聲明式事務管理兩種方式。
程式設計式事務管理使用TransactionTemplate或者直接使用底層的PlatformTransactionManager。對于程式設計式事務管理,spring推薦使用TransactionTemplate。
聲明式事務管理建立在AOP之上的。其本質是對方法前後進行攔截,然後在目标方法開始之前建立或者加入一個事務,在執行完目标方法之後根據執行情況送出或者復原事務。聲明式事務最大的優點就是不需要通過程式設計的方式管理事務,這樣就不需要在業務邏輯代碼中摻雜事務管理的代碼,隻需在配置檔案中做相關的事務規則聲明(或通過基于@Transactional注解的方式),便可以将事務規則應用到業務邏輯中。
顯然聲明式事務管理要優于程式設計式事務管理,這正是spring倡導的非侵入式的開發方式。聲明式事務管理使業務代碼不受污染,一個普通的POJO對象,隻要加上注解就可以獲得完全的事務支援。和程式設計式事務相比,聲明式事務唯一不足地方是,後者的最細粒度隻能作用到方法級别,無法做到像程式設計式事務那樣可以作用到代碼塊級别。但是即便有這樣的需求,也存在很多變通的方法,比如,可以将需要進行事務管理的代碼塊獨立為方法等等。
聲明式事務管理也有兩種常用的方式,一種是基于tx和aop名字空間的xml配置檔案,另一種就是基于@Transactional注解。顯然基于注解的方式更簡單易用,更清爽。
自動送出(AutoCommit)與連接配接關閉時的是否自動送出
自動送出
預設情況下,資料庫處于自動送出模式。每一條語句處于一個單獨的事務中,在這條語句執行完畢時,如果執行成功則隐式的送出事務,如果
執行失敗則隐式的復原事務。
對于正常的事務管理,是一組相關的操作處于一個事務之中,是以必須關閉資料庫的自動送出模式。不過,這個我們不用擔心,spring會将底層連接配接的自動送出特性設定為false。
org/springframework/jdbc/datasource/DataSourceTransactionManager.java

1 // switch to manual commit if necessary. this is very expensive in some jdbc drivers,
2 // so we don't want to do it unnecessarily (for example if we've explicitly
3 // configured the connection pool to set it already).
4 if (con.getautocommit()) {
5 txobject.setmustrestoreautocommit(true);
6 if (logger.isdebugenabled()) {
7 logger.debug("switching jdbc connection [" + con + "] to manual commit");
8 }
9 con.setautocommit(false);
10 }

有些資料連接配接池提供了關閉事務自動送出的設定,最好在設定連接配接池時就将其關閉。但C3P0沒有提供這一特性,隻能依靠spring來設定。
因為JDBC規範規定,當連接配接對象建立時應該處于自動送出模式,這是跨DBMS的預設值,如果需要,必須顯式的關閉自動送出。C3P0遵守這一規範,讓客戶代碼來顯式的設定需要的送出模式。
連接配接關閉時的是否自動送出
當一個連接配接關閉時,如果有未送出的事務應該如何處理?JDBC規範沒有提及,C3P0預設的政策是復原任何未送出的事務。這是一個正确的政策,但JDBC驅動提供商之間對此問題并沒有達成一緻。
C3P0的autoCommitOnClose屬性預設是false,沒有十分必要不要動它。或者可以顯式的設定此屬性為false,這樣會更明确。
基于注解的聲明式事務管理配置
spring-servlet.xml

1 <!-- transaction support-->
2 <!-- PlatformTransactionMnager -->
3 <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
4 <property name="dataSource" ref="dataSource" />
5 </bean>
6 <!-- enable transaction annotation support -->
7 <tx:annotation-driven transaction-manager="txManager" />

還要在spring-servlet.xml中添加tx名字空間

1 ...
2 xmlns:tx="http://www.springframework.org/schema/tx"
3 xmlns:aop="http://www.springframework.org/schema/aop"
4 xsi:schemaLocation="
5 ...
6
7 http://www.springframework.org/schema/tx
8
9
10 http://www.springframework.org/schema/tx/spring-tx.xsd
11
12 ...

MyBatis自動參與到spring事務管理中,無需額外配置,隻要org.mybatis.spring.SqlSessionFactoryBean引用的資料源與DataSourceTransactionManager引用的資料源一緻即可,否則事務管理會不起作用。
另外需要下載下傳依賴包aopalliance.jar放置到WEB-INF/lib目錄下。否則spring初始化時會報異常
java.lang.NoClassDefFoundError: org/aopalliance/intercept/MethodInterceptor
spring事務特性
spring所有的事務管理政策類都繼承自org.springframework.transaction.PlatformTransactionManager接口

1 public interface PlatformTransactionManager {
2
3 TransactionStatus getTransaction(TransactionDefinition definition)
4 throws TransactionException;
5
6 void commit(TransactionStatus status) throws TransactionException;
7
8 void rollback(TransactionStatus status) throws TransactionException;
9 }

其中TransactionDefinition接口定義以下特性:
事務隔離級别
隔離級别是指若幹個并發的事務之間的隔離程度。TransactionDefinition 接口中定義了五個表示隔離級别的常量:
- TransactionDefinition.ISOLATION_DEFAULT:這是預設值,表示使用底層資料庫的預設隔離級别。對大部分資料庫而言,通常這值就是TransactionDefinition.ISOLATION_READ_COMMITTED。
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED:該隔離級别表示一個事務可以讀取另一個事務修改但還沒有送出的資料。該級别不能防止髒讀,不可重複讀和幻讀,是以很少使用該隔離級别。比如PostgreSQL實際上并沒有此級别。
- TransactionDefinition.ISOLATION_READ_COMMITTED:該隔離級别表示一個事務隻能讀取另一個事務已經送出的資料。該級别可以防止髒讀,這也是大多數情況下的推薦值。
- TransactionDefinition.ISOLATION_REPEATABLE_READ:該隔離級别表示一個事務在整個過程中可以多次重複執行某個查詢,并且每次傳回的記錄都相同。該級别可以防止髒讀和不可重複讀。
- TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事務依次逐個執行,這樣事務之間就完全不可能産生幹擾,也就是說,該級别可以防止髒讀、不可重複讀以及幻讀。但是這将嚴重影響程式的性能。通常情況下也不會用到該級别。
事務傳播行為
所謂事務的傳播行為是指,如果在開始目前事務之前,一個事務上下文已經存在,此時有若幹選項可以指定一個事務性方法的執行行為。在TransactionDefinition定義中包括了如下幾個表示傳播行為的常量:
- TransactionDefinition.PROPAGATION_REQUIRED:如果目前存在事務,則加入該事務;如果目前沒有事務,則建立一個新的事務。這是預設值。
- TransactionDefinition.PROPAGATION_REQUIRES_NEW:建立一個新的事務,如果目前存在事務,則把目前事務挂起。
- TransactionDefinition.PROPAGATION_SUPPORTS:如果目前存在事務,則加入該事務;如果目前沒有事務,則以非事務的方式繼續運作。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事務方式運作,如果目前存在事務,則把目前事務挂起。
- TransactionDefinition.PROPAGATION_NEVER:以非事務方式運作,如果目前存在事務,則抛出異常。
- TransactionDefinition.PROPAGATION_MANDATORY:如果目前存在事務,則加入該事務;如果目前沒有事務,則抛出異常。
- TransactionDefinition.PROPAGATION_NESTED:如果目前存在事務,則建立一個事務作為目前事務的嵌套事務來運作;如果目前沒有事務,則該取值等價于TransactionDefinition.PROPAGATION_REQUIRED。
事務逾時
所謂事務逾時,就是指一個事務所允許執行的最長時間,如果超過該時間限制但事務還沒有完成,則自動復原事務。在 TransactionDefinition 中以 int 的值來表示逾時時間,其機關是秒。
預設設定為底層事務系統的逾時值,如果底層資料庫事務系統沒有設定逾時值,那麼就是none,沒有逾時限制。
事務隻讀屬性
隻讀事務用于客戶代碼隻讀但不修改資料的情形,隻讀事務用于特定情景下的優化,比如使用Hibernate的時候。
預設為讀寫事務。
spring事務復原規則
訓示spring事務管理器復原一個事務的推薦方法是在目前事務的上下文内抛出異常。spring事務管理器會捕捉任何未處理的異常,然後依據規則決定是否復原抛出異常的事務。
預設配置下,spring隻有在抛出的異常為運作時unchecked異常時才復原該事務,也就是抛出的異常為RuntimeException的子類(Errors也會導緻事務復原),而抛出checked異常則不會導緻事務復原。
可以明确的配置在抛出那些異常時復原事務,包括checked異常。也可以明确定義那些異常抛出時不復原事務。
還可以程式設計性的通過setRollbackOnly()方法來訓示一個事務必須復原,在調用完setRollbackOnly()後你所能執行的唯一操作就是復原。
@Transactional注解
@Transactional屬性
屬性 | 類型 | 描述 |
---|---|---|
value | String | 可選的限定描述符,指定使用的事務管理器 |
propagation | enum: Propagation | 可選的事務傳播行為設定 |
isolation | enum: Isolation | 可選的事務隔離級别設定 |
readOnly | boolean | 讀寫或隻讀事務,預設讀寫 |
timeout | int (in seconds granularity) | 事務逾時時間設定 |
rollbackFor | Class對象數組,必須繼承自Throwable | 導緻事務復原的異常類數組 |
rollbackForClassName | 類名數組,必須繼承自Throwable | 導緻事務復原的異常類名字數組 |
noRollbackFor | Class對象數組,必須繼承自Throwable | 不會導緻事務復原的異常類數組 |
noRollbackForClassName | 類名數組,必須繼承自Throwable | 不會導緻事務復原的異常類名字數組 |
用法
@Transactional 可以作用于接口、接口方法、類以及類方法上。當作用于類上時,該類的所有 public 方法将都具有該類型的事務屬性,同時,我們也可以在方法級别使用該标注來覆寫類級别的定義。
雖然 @Transactional 注解可以作用于接口、接口方法、類以及類方法上,但是 Spring 建議不要在接口或者接口方法上使用該注解,因為這隻有在使用基于接口的代理時它才會生效。另外, @Transactional 注解應該隻被應用到 public 方法上,這是由 Spring AOP 的本質決定的。如果你在 protected、private 或者預設可見性的方法上使用 @Transactional 注解,這将被忽略,也不會抛出任何異常。
預設情況下,隻有來自外部的方法調用才會被AOP代理捕獲,也就是,類内部方法調用本類内部的其他方法并不會引起事務行為,即使被調用方法使用@Transactional注解進行修飾。

1 @Transactional(readOnly = true)
2 public class DefaultFooService implements FooService {
3
4 public Foo getFoo(String fooName) {
5 // do something
6 }
7
8 // these settings have precedence for this method
9 //方法上注解屬性會覆寫類注解上的相同屬性
10 @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
11 public void updateFoo(Foo foo) {
12 // do something
13 }
14 }
