Spring事務管理—快速入門
先來講講臟讀 不可重復讀 和 幻讀。
- 臟讀:我們在并發編程中是很熟悉的,通俗的講就是你讀得數據已經被修改了,已經過時失去意義了。
- 不可重復讀: 同一個事務里面多次讀取同一行數據,卻返回不同的結果。
- 幻讀:同樣一筆查詢在整個事務過程中多次執行后,查詢所得的結果集不一樣。
事務四大特性 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事務傳播機制
事務傳播行為 | 含義 |
PROPAGATION_REQUIRED(默認) | 必須在事務中執行,如果沒有,就新new一個新事務 |
PROPAGATION_SUPPORTS | 誰調用我我就在誰的事務中執行,沒有的話就沒有 |
PROPAGATION_MANDATORY | 必須要有事務,沒有就報錯 |
PROPAGATION_REQUIRED_NEW | 不管調用我的方法有沒有事務,我都new一個事務 |
PROPAGATION_NOT_SUPPORTED | 調用我的方法有事務,但我不在事務中執行 |
PROPAGATION_NEVER | 不允許在事務中運行,有事務則報錯 |
PROPAGATION_NESTED | 有事務則嵌套,沒有則new一個新事務 |
從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的事務管理,其它框架也類似。