使用Spring實現反應式事務(Reactive Transactions)

banq發表於2019-05-25

本文探討如何使用RDBC2或MongoDB來使用Spring Reactive的事務支援。

在還沒有加入響應式/反應式事務整合之間,Spring認為沒有必須進行Reactive事務管理,因此,Spring Framework不支援Reactive @Transaction。

隨著時間的推移,MongoDB開始支援MongoDB Server 4.0的多文件事務,R2DBC(反應式SQL資料庫驅動程式的規範)開始出現,最終在Template API中提供inTransaction(…) 方法作為執行原生本級事務的工作單元。

雖然將inTransaction(…)方法用於較小的工作塊很方便,但它並不反映Spring支援事務的方式。在使用指令式程式設計模型時,Spring Framework允許兩種事務管理安排:@Transactional和TransactionTemplate(宣告性的各自的程式化事務管理)。

這兩種事務管理方法都建立在PlatformTransactionManager管理事務資源事務的基礎之上。PlatformTransactionManager可以是Spring提供的事務管理器實現,也可以是基於J他的Java EE實現。

兩種方法的共同之處在於它們將事務狀態繫結到ThreadLocal儲存,這允許事務狀態管理而不傳遞TransactionStatus物件。事務管理應該在後臺以非侵入方式進行。因為我們沒有讓執行緒繼續在事務中繼續有作用工作的設想,因此ThreadLocal只在指令式程式設計中工作。

指令式程式設計事務管理工作機制

事務管理需要將其事務狀態與執行相關聯。在指令式程式設計中,這通常是ThreadLocal儲存 - 事務狀態被繫結到一個執行緒,假設前提是事務程式碼在容器呼叫它的同一個執行緒上執行。

反應式程式設計模型消除了命令式(同步/阻塞)程式設計模型的這一基本假設。仔細研究反應式執行情況,你會發現程式碼在不同的執行緒上執行。使用程式間通訊時,這會更加明顯。我們再也不能安全地假設我們的程式碼在同一個執行緒上完全執行了。

這種變化使的依賴ThreadLocal的事務管理實現無效。

我們需要一種不同的安排來反映事務狀態,而不是一直傳遞一個TransactionStatus物件。

關聯帶外資料並不是反應空間中的新要求。我們在其他領域遇到過這種要求,例如SecurityContextSpring Security for reactive方法安全性(僅舉一例)。Project Reactor是Spring自身構建其響應支援的反應庫,自3.1版本開始就為訂閱者的上下文提供了支援。

Reactor Context是替代ThreadLocal指令式程式設計的反應式程式設計,上下文允許將上下文資料繫結到特定的執行。對於反應式程式設計,這是一個Subscription。

Reactor Context允許Spring將事務狀態以及所有資源和同步繫結到特定的Subscription狀態。使用Project Reactor的所有反應程式碼現在都可以參與響應式事務。

反應性事務管理

從Spring Framework 5.2 M2開始,Spring通過ReactiveTransactionManagerSPI 支援響應式/反應式事務管理。

ReactiveTransactionManager是使用事務資源的反應式和非阻塞整合的事務管理抽象。它是一個會返回Publisher的反應式@Transactional方法元註解,使用TransactionalOperator實現可程式設計的事務管理。

兩個反應式事務管理器實現是:

  • R2DBC通過Spring Data R2DBC 1.0 M2
  • MongoDB通過Spring Data MongoDB 2.2 M4

讓我們來看看反應式式事務的樣子:

class TransactionalService {

  final DatabaseClient db

  TransactionalService(DatabaseClient db) {
    this.db = db;
  }

  @Transactional
  Mono<Void> insertRows() {

    return db.execute()
      .sql("INSERT INTO person (name, age) VALUES('Joe', 34)")
      .fetch().rowsUpdated()
      .then(db.execute().sql("INSERT INTO contacts (name) VALUES('Joe')")
      .then();
  }
}

反應事務看起來非常類似於註釋驅動中的命令事務,主要的區別在於我們使用DatabaseClient,這是一個反應性資源抽象。所有事務管理都在幕後進行,利用Spring的事務攔截器和ReactiveTransactionManager。

Spring基於方法返回型別分辨要應用的事務管理型別:

  • 方法返回一個Publisher型別:響應式事務管理
  • 所有其他return型別:傳統的命令式事務管理

這種區別很重要,因為您仍然可以使用命令式元件,例如JPA或JDBC查詢,如果將這些查詢結果包裝成一個Publisher型別,Spring將應用反應而不是命令式事務管理,反應式事務管理就不會開啟ThreadLocal中繫結的JPA或JDBC所需的事務。

TransactionalOperator

下一步,讓我們看一下程式設計化事務管理TransactionalOperator:

ConnectionFactory factory = …
ReactiveTransactionManager tm = new R2dbcTransactionManager(factory);
DatabaseClient db = DatabaseClient.create(factory);

TransactionalOperator rxtx = TransactionalOperator.create(tm);

Mono<Void> atomicOperation = db.execute()
  .sql("INSERT INTO person (name, age) VALUES('joe', 'Joe')")
  .fetch().rowsUpdated()
  .then(db.execute()
    .sql("INSERT INTO contacts (name) VALUES('Joe')")
    .then())
  .as(rxtx::transactional);

上面的程式碼包含一些值得注意的元件:

  • R2dbcTransactionManager:這是R2DBC的反應式事務管理器ConnectionFactory。
  • DatabaseClient:客戶端使用R2DBC驅動程式提供對SQL資料庫的訪問。
  • TransactionalOperator:此運算子將所有上游R2DBC釋出者與事務上下文相關聯。您可以使用操作員樣式as(…::transactional)或回撥樣式execute(txStatus -> …)。

訂閱後會懶惰地反應式事務,operator啟動事務,設定適當的隔離級別並將資料庫連線與其訂戶上下文相關聯。所有參與(上游)Publisher例項都使用一個上下文繫結事務連線。

Reactive-functional operator 鏈可以是線性的(通過使用單個Publisher)或非線性的(通過合併多個流)。Publisher使用operator風格樣式時,反應式事務將會影響所有上游。要將事務範圍限制為特定的Publishers 集合,請應用回撥樣式,如下所示:

TransactionalOperator rxtx = TransactionalOperator.create(tm);

Mono<Void> outsideTransaction = db.execute()
  .sql("INSERT INTO person (name, age) VALUES('Jack', 31)")
  .then();

Mono<Void> insideTransaction = rxtx.execute(txStatus -> {
  return db.execute()
    .sql("INSERT INTO person (name, age) VALUES('Joe', 34)")
    .fetch().rowsUpdated()
    .then(db.execute()
      .sql("INSERT INTO contacts (name) VALUES('Joe Black')")
      .then());
  }).then();

Mono<Void> completion = outsideTransaction.then(insideTransaction);

在上面的示例中,事務管理僅限於在execute(…)裡面訂閱的Publisher例項。換句話說,事務是作用域的。execute(…)中Publisher例項參與事務,並且命名outsideTransaction的Publisher在事務之外執行其工作。

Spring Data MongoDB

R2DBC是Spring與反應式的整合之一。另一個事務整合是通過Spring Data MongoDB訪問MongoDB,您可以使用反應式程式設計來參與多文件事務。

Spring Data MongoDB是使用ReactiveMongoTransactionManager,這是一個ReactiveTransactionManager實現。它建立會話並管理事務,以便在託管事務中執行的程式碼參與多文件事務。

以下示例顯示了MongoDB的程式設計事務管理:

ReactiveTransactionManager tm 
  = new ReactiveMongoTransactionManager(databaseFactory);
ReactiveMongoTemplate template = …
template.setSessionSynchronization(ALWAYS);                                          

TransactionalOperator rxtx = TransactionalOperator.create(tm);

Mono<Void> atomic = template.update(Step.class)
  .apply(Update.set("state", …))
  .then(template.insert(EventLog.class).one(new EventLog(…))
  .as(rxtx::transactional)
  .then();

上面的程式碼設定一個ReactiveTransactionManager並用於TransactionalOperator在單個事務中執行多個寫操作。ReactiveMongoTemplate被配置為參與反應式交易。

相關文章