1. 程式人生 > 實用技巧 >Spring 事務概述

Spring 事務概述

本文是我們分散式事務系列的第三篇,這篇文章來和大家捋一捋 Spring 框架中的事務體系。前面兩篇文章大家可以參考:

  1. 分散式事務開局第一篇,從資料庫事務隔離級別說起
  2. 分散式事務系列第二篇,回顧 Jdbc 事務

Spring 作為 Java 開發中的基礎設施,對於事務也提供了很好的支援,總體上來說,Spring 支援兩種型別的事務,宣告式事務和程式設計式事務。

程式設計式事務類似於 Jdbc 事務的寫法,需要將事務的程式碼嵌入到業務邏輯中,這樣程式碼的耦合度較高,而宣告式事務通過 AOP 的思想能夠有效的將事務和業務邏輯程式碼解耦,因此在實際開發中,宣告式事務得到了廣泛的應用,而程式設計式事務則較少使用,考慮到文章內容的完整,本文對兩種事務方式都會介紹。

1.核心類

Spring 中對事務的處理,涉及到三個核心類:

  1. PlatformTransactionManager
  2. TransactionDefinition
  3. TransactionStatus

這三個核心類是Spring處理事務的核心類。

1.1 PlatformTransactionManager

PlatformTransactionManager 是事務處理的核心,它有諸多的實現類,如下:

3-1

PlatformTransactionManager 的定義如下:

public interface PlatformTransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition);
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}

可以看到 PlatformTransactionManager 中定義了基本的事務操作方法,這些事務操作方法都是平臺無關的,具體的實現都是由不同的子類來實現的。PlatformTransactionManager 中主要有如下三個方法:

「1.getTransaction()」

getTransaction() 是根據傳入的 TransactionDefinition 獲取一個事務物件,TransactionDefinition 中定義了一些事務的基本規則,例如傳播性、隔離級別等。

「2.commit()」

commit() 方法用來提交事務。

「3.rollback()」

rollback() 方法用來回滾事務。

PlatformTransactionManager 有多個實現類,這些實現類就是 Spring 內建的事務管理器,如下:

3-2

1.2 TransactionDefinition

TransactionDefinition 用來描述事務的具體規則,該類中的方法如下:

3-2

可以看到一共有五個方法:

  1. getIsolationLevel(),獲取事務的隔離級別
  2. getName(),獲取事務的名稱
  3. getPropagationBehavio(),獲取事務的傳播性
  4. getTimeout(),獲取事務的超時時間
  5. isReadOnly(),獲取事務是否是隻讀事務

TransactionDefinition 也有諸多的實現類,如下:

3-3

如果開發者使用了程式設計式事務的話,直接使用 DefaultTransactionDefinition 即可。

1.2.1 隔離級別

這裡事務的隔離級別,主要有如下幾種:

public enum Isolation {
DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),
READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),
READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),
REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),
SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);
private final int value;
Isolation(int value) { this.value = value; }
public int value() { return this.value; }
}

其中,DEFAULT 表示使用資料庫預設的事務隔離級別,其他的隔離級別則和資料庫中的隔離級別一一對應。

1.2.2 傳播性

關於事務的傳播性,Spring 主要定義瞭如下幾種:

public enum Propagation {
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
NEVER(TransactionDefinition.PROPAGATION_NEVER),
NESTED(TransactionDefinition.PROPAGATION_NESTED);
private final int value;
Propagation(int value) { this.value = value; }
public int value() { return this.value; }
}

具體含義如下:

其中,PROPAGATION_NESTED 和 PROPAGATION_REQUIRES_NEW 區別如下:

1. 開啟事務的多少,PROPAGATION_REQUIRES_NEW 會開啟一個新事務,外部事務掛起,裡面的事務獨立執行。PROPAGATION_NESTED 為父子事務,實際上是藉助 jdbc 的 savepoint 實現的,屬於同一個事物。
2. PROPAGATION_NESTED 的回滾可以總結為,子事務回滾到 savepoint,父事務可選擇性回滾或者不不滾;父事務回滾子事務一定回滾。PROPAGATION_REQUIRES_NEW 則是不同事物,巢狀事務之間沒有必然聯絡是否回滾都由自己決定。

1.3 TransactionStatus

TransactionStatus 可以直接理解為事務本身,該介面原始碼如下:

public interface TransactionStatus extends SavepointManager, Flushable {
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
void flush();
boolean isCompleted();
}
  1. isNewTransaction() 方法獲取當前事務是否是一個新事務。
  2. hasSavepoint() 方法判斷是否存在 savePoint()。
  3. setRollbackOnly() 方法設定事務必須回滾。
  4. isRollbackOnly() 方法獲取事務只能回滾。
  5. flush() 方法將底層會話中的修改重新整理到資料庫,一般用於 Hibernate/JPA 的會話,對如 JDBC 型別的事務無任何影響。
  6. isCompleted() 方法用來獲取是一個事務是否結束。

2.程式設計式事務

程式設計式事務並非主流用法,但是為了使我們的知識點完整,這裡還是給讀者介紹下程式設計式事務的用法。「本系列所有案例都是基於 SpringBoot,因此涉及到 SpringBoot 相關的知識點,預設認為讀者都懂。如果大家對 Spring Boot 尚不熟悉的話,可以先看看鬆哥關於 Spring Boot 的視訊教程:Spring Boot + Vue 視訊教程

程式設計式事務整體上來說有兩種實現方式,下面分別予以介紹。

2.1 基於 Spring 基本的 API

這裡還是使用前文的轉賬資料庫,依然模擬一個轉賬操作。

本案例使用的資料庫操作庫為 Spring 自帶的 JdbcTemplate,當然,讀者也可以使用 MyBatis 或者其他庫。JdbcTemplate 配置過程這裡就省略了(不懂配置,可以參考Spring Boot 整合 JdbcTemplate),我們來直接看轉賬的核心操作,如下:

@Service
public class TransferService {
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
PlatformTransactionManager txManager;

public void transfer() {
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
definition.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
TransactionStatus status = txManager.getTransaction(definition);
try {
jdbcTemplate.update("update user set account=account+100 where username='zhangsan'");
int i = 1 / 0;
jdbcTemplate.update("update user set account=account-100 where username='lisi'");
txManager.commit(status);
} catch (DataAccessException e) {
e.printStackTrace();
txManager.rollback(status);
}
}
}

關於這段程式碼,我做如下幾點解釋:

  1. 首先注入 JdbcTemplate,這是由 SpringBoot 自動配置類提供的,這個很簡單,這裡不贅述。
  2. 接下來注入 PlatformTransactionManager,本案例中 PlatformTransactionManager 的實現類是 DataSourceTransactionManager,這是由於開發者沒有提供 PlatformTransactionManager 的例項,並且當前專案 claspath 下存在 JdbcTemplate,相關原始碼如下:
@Configuration
@ConditionalOnClass({ JdbcTemplate.class, PlatformTransactionManager.class })
@AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceTransactionManagerAutoConfiguration {

@Configuration
@ConditionalOnSingleCandidate(DataSource.class)
static class DataSourceTransactionManagerConfiguration {
...
...
@Bean
@ConditionalOnMissingBean(PlatformTransactionManager.class)
public DataSourceTransactionManager transactionManager(
DataSourceProperties properties) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(
this.dataSource);
if (this.transactionManagerCustomizers != null) {
this.transactionManagerCustomizers.customize(transactionManager);
}
return transactionManager;
}
}
}
  1. 在 transfer 方法中,首先定義一個 DefaultTransactionDefinition,然後定義事務的隔離級別,再通過 txManager 中的 getTransaction 方法獲取一個 TransactionStatus 的例項,最後在 try..cache 中處理業務,也方法順利執行時提交事務,否則回滾事務。

2.2 基於 TransactionTemplate

考慮到基於 Spring 基礎事務 API 實現的事務管理是一個模板化的操作,Spring 提供了另外一個類 TransactionTemplate,來簡化這個模板操作,在 SpringBoot 的 TransactionAutoConfiguration 類中,可以看到 SpringBoot 已經為開發者提供了 TransactionTemplate 例項,如下:

@Configuration
@ConditionalOnClass(PlatformTransactionManager.class)
@AutoConfigureAfter({ JtaAutoConfiguration.class, HibernateJpaAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
Neo4jDataAutoConfiguration.class })
@EnableConfigurationProperties(TransactionProperties.class)
public class TransactionAutoConfiguration {
...
...
@Configuration
@ConditionalOnSingleCandidate(PlatformTransactionManager.class)
public static class TransactionTemplateConfiguration {

private final PlatformTransactionManager transactionManager;
public TransactionTemplateConfiguration(
PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
@Bean
@ConditionalOnMissingBean
public TransactionTemplate transactionTemplate() {
return new TransactionTemplate(this.transactionManager);
}
}
...
...
}

由於 TransactionTemplate 的例項系統已經配置好了,因此開發者可以直接使用 TransactionTemplate,如下:

@Service
public class TransferService {
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
TransactionTemplate tranTemplate;
public void transfer() {
tranTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
jdbcTemplate.update("update user set account=account+100 where username='zhangsan'");
int i = 1 / 0;
jdbcTemplate.update("update user set account=account-100 where username='lisi'");
} catch (DataAccessException e) {
status.setRollbackOnly();
e.printStackTrace();
}
}
});
}
}

直接注入 TransactionTemplate,然後在 execute 方法中添加回調寫核心的業務即可,當丟擲異常時,將當前事務標註為只能回滾即可。注意,execute 方法中,如果不需要獲取事務執行的結果,則直接使用 TransactionCallbackWithoutResult 類即可,如果要獲取事務執行結果,則使用 TransactionCallback 即可。

3.宣告式事務

程式設計式事務會干擾到業務邏輯程式碼,而宣告式事務不會,這是宣告式事務的最大優勢。

宣告式事務的使用比較簡單,在 SpringBoot 專案中,直接使用 @Transactional 註解即可實現,如下:

@Service
public class TransferService {
@Autowired
JdbcTemplate jdbcTemplate;
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void transfer() {
jdbcTemplate.update("update user set account=account+100 where username='zhangsan'");
int i = 1 / 0;
jdbcTemplate.update("update user set account=account-100 where username='lisi'");
}
}

開發者可以在 @Transactional 註解中配置事務的隔離級別,傳播性等。

參考:

  1. https://blog.csdn.net/Big_Blogger/article/details/70184627