Reactive Transactions with Spring

Engineering
Mark Paluch
May 16, 2019

Back in 2016, our reactive journey started with Spring Framework 5 accompanied by a couple of reactive integrations. Throughout our journey, other projects joined the reactive movement. With R2DBC, we now also provide a reactive integration for SQL databases. With the growth of transaction-capable integrations, we constantly got asked:
Does Spring Framework support Reactive @Transaction?

At the time our journey began, we had no reactive form of transactional integrations, so this question was simple to answer: There’s no need for reactive transaction management.

Over time, MongoDB started to support multi-document transactions with MongoDB Server 4.0. R2DBC (the specification for reactive SQL database drivers) started to emerge, and we decided to pick up on R2DBC with Spring Data R2DBC. Both projects wanted to expose transactional behavior, so they eventually provided inTransaction(…) methods on their Template APIs to perform units of work guarded by native transactions.

While it is convenient to use an inTransaction(…) method for smaller chunks of work, it does not reflect the Spring way of supporting transactions. When working with imperative programming models, Spring Framework allows for two arrangements of transaction management: @Transactional and TransactionTemplate (declarative respective programmatic transaction management).

Both approaches to transaction management are built on top of PlatformTransactionManager, which manages transactions for transactional resources. PlatformTransactionManager can be either a Spring-provided transaction manager implementation or a Java EE one based on JTA.

Both approaches have in common that they bind the transactional state to ThreadLocal storage, which allows for transactional state management without passing a TransactionStatus object. Transaction management should happen in the background in a non-invasive manner.
ThreadLocal works in imperative programming arrangements because of the underlying assumption that we do not engage threads to continue work within a transaction.
How Imperative Transaction Management Works

Transaction management needs to associate its transactional state with an execution. In imperative programming, this is typically a ThreadLocal storage – Transactional state is bound to a Thread. The underlying assumption is that transactional code gets executed on the same thread on which the container has invoked it.

Reactive programming models remove this fundamental assumption of imperative (synchronous/blocking) programming models. Taking a closer look at reactive execution, we can observe that code gets executed on different threads. This gets more visible when using inter-process communication. We can no longer safely assume that our code is fully executed on the same thread.

This change in assumptions invalidates transaction management implementations that rely on ThreadLocal.

Thread switches happen at arbitrary times, due to integrations and optimizations such as operator fusion. This change breaks all code that relies on ThreadLocal. The consequence is that we need a different arrangement to reflect transactional state without passing a TransactionStatus object all the time.

Associating out-of-band data is not a new requirement in the reactive space. We faced this requirement in other areas, such as the SecurityContext with Spring Security for reactive method security (to name one example). Project Reactor, the reactive library on top of which Spring builds its reactive support, has provided support for subscriber contexts since version 3.1.

Reactor Context is to reactive programming what ThreadLocal is to imperative programming: Contexts allow binding contextual data to a particular execution. For reactive programming, this is a Subscription. Reactor’s Context lets Spring bind the transaction state, along with all resources and synchronizations, to a particular Subscription. All reactive code that uses Project Reactor can now participate in reactive transactions. Code that returns scalar values and that wants to access transactional details must be rewritten to use reactive types to participate in transactions. Otherwise, the Context is not available.
Reactive Transaction Management

Starting with Spring Framework 5.2 M2, Spring supports reactive transaction management through the ReactiveTransactionManager SPI.

ReactiveTransactionManager is a transaction management abstraction for reactive and non-blocking integrations that uses transactional resources. It is a foundation for reactive @Transactional methods that return Publisher types and for programmatic transaction management that uses TransactionalOperator.

The first two reactive transaction manager implementations are:

R2DBC through Spring Data R2DBC 1.0 M2
MongoDB through Spring Data MongoDB 2.2 M4

Let’s take a look at how reactive transactions look like:

class TransactionalService {

final DatabaseClient db

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

@Transactional
Mono 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();

}
}

Reactive transactions look very similar to imperative transactions in annotation-driven arrangements. The main difference though is that we work with DatabaseClient, which is a reactive resource abstraction. All transaction management happens behind the scenes, leveraging Spring’s transaction interceptors and ReactiveTransactionManager.

Spring distinguishes (based on method return types) which type of transaction management to apply:

Method returns a Publisher type: Reactive Transaction Management
All other return types: Imperative Transaction Management

This distinction is significant, as you could still use imperative components such as a JPA or JDBC query. Wrapping these results into a Publisher type signals Spring to apply reactive rather than imperative transaction management. That being said, a reactive transaction arrangement does not open a ThreadLocal-bound transaction, which is required for JPA or JDBC.
TransactionalOperator

As a next step, let’s take a look at programmatic transaction management by using TransactionalOperator:

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

TransactionalOperator rxtx = TransactionalOperator.create™;

Mono 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);

The code above contains some notable components:

R2dbcTransactionManager: This is the reactive transaction manager for a R2DBC ConnectionFactory .
DatabaseClient: The client provides access to SQL databases using R2DBC drivers.
TransactionalOperator: This operator associates all upstream R2DBC publishers with a transactional context. You can use it either operator style as(…::transactional) or call-back style with execute(txStatus -> …).

Reactive transactions are started lazily upon subscription. The operator starts a transaction, sets the appropriate isolation level and associates the database connection with its subscriber context. All participating (upstream) Publisher instances use a single Context-bound transactional connection.

Reactive-functional operator chains can be either linear (by using a single Publisher) or non-linear (by merging multiple streams). Reactive transactions affect all upstream Publishers when using operator style. To limit the transaction scope to a particular set of Publishers, apply callback style, as follows:

TransactionalOperator rxtx = TransactionalOperator.create™;

Mono outsideTransaction = db.execute()
.sql(“INSERT INTO person (name, age) VALUES(‘Jack’, 31)”)
.then();

Mono 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 completion = outsideTransaction.then(insideTransaction);

In the example above, transaction management is limited to Publisher instances subscribed within execute(…). Or, to put it differently, the transaction is scoped. Publisher instances within execute(…) participate in the transaction, and the Publisher named outsideTransaction performs its work outside the transaction.

R2DBC is one of Spring’s integrations with reactive transactions. Another integration is MongoDB through Spring Data MongoDB, which you can use to participate in multi-document transactions by using reactive programming.

Spring Data MongoDB ships with ReactiveMongoTransactionManager as a ReactiveTransactionManager implementation. It creates a session and manages transactions so that code executed within a managed transaction participates in multi-document transactions.

The following example shows programmatic transaction management with MongoDB:

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

TransactionalOperator rxtx = TransactionalOperator.create™;

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

The code above sets up a ReactiveTransactionManager and uses TransactionalOperator to perform multiple write actions within a single transaction. ReactiveMongoTemplate gets configured to participate in reactive transactions.
Next Steps

Reactive Transaction Management ships with Spring Framework 5.2 M2, Spring Data MongoDB 2.2 M4, and Spring Data R2DBC 1.0 M2 milestone releases. You can pick up these and start integrating reactive transaction management in your code. We look forward to community feedback so that we can smooth out any sharp edges before shipping release candidates in early June.
comments powered by Disqus

translate:
翻译:

早在2016年,我们的响应式旅程就从Spring Framework 5开始,伴随着几个响应式集成。在我们的整个旅程中,其他项目也加入了反动运动。借助R2DBC,我们现在还为SQL数据库提供了反应式集成。随着具有事务处理能力的集成的发展,我们不断被问到:
Spring Framework是否支持Reactive @Transaction?

在我们的旅程开始之时,我们还没有被动式的交易集成形式,因此这个问题很容易回答:不需要被动式的交易管理。

随着时间的流逝,MongoDB开始使用MongoDB Server 4.0支持多文档事务。 R2DBC(用于响应式SQL数据库驱动程序的规范)开始出现,我们决定使用Spring Data R2DBC选择R2DBC。这两个项目都希望公开交易行为,因此他们最终在其模板API上提供了inTransaction(…)方法,以执行受本机交易保护的工作单元。

虽然使用inTransaction(…)方法处理较小的工作块很方便,但它并不能反映Spring支持事务的方式。当使用命令式编程模型时,Spring Framework允许事务管理的两种安排:@Transactional和TransactionTemplate(分别声明式编程事务管理)。

两种事务管理方法都建立在PlatformTransactionManager之上,后者管理事务资源的事务。 PlatformTransactionManager可以是Spring提供的事务管理器实现,也可以是基于JTA的Java EE。

两种方法的共同点是它们将事务状态绑定到ThreadLocal存储,这允许在不传递TransactionStatus对象的情况下进行事务状态管理。交易管理应在后台以非侵入方式进行。
ThreadLocal以命令式编程的方式工作,因为这种基本假设是,我们不使用线程来继续事务中的工作。
命令性交易管理如何工作

事务管理需要将其事务状态与执行关联。在命令式编程中,这通常是ThreadLocal存储–事务状态绑定到Thread。基本假设是事务代码在容器调用它的同一线程上执行。

反应式编程模型消除了命令式(同步/阻塞)编程模型的基本假设。仔细研究响应式执行,我们可以观察到代码在不同的线程上执行。使用进程间通信时,这一点更加明显。我们不能再安全地假设我们的代码已在同一线程上完全执行。

假设的这种更改会使依赖ThreadLocal的事务管理实现无效。

由于集成和优化(例如运算符融合),线程切换在任意时间发生。此更改将破坏所有依赖ThreadLocal的代码。结果是我们需要一种不同的安排来反映事务状态,而不必始终传递TransactionStatus对象。

关联带外数据并不是反应空间中的新要求。我们在其他领域也遇到了这一要求,例如带有响应式方法安全性的Spring Security的SecurityContext(举一个例子)。从3.1版开始,Project Reactor是Spring在其之上构建其反应式支持的反应式库,它为订户上下文提供了支持。

Reactor Context是响应式编程,而ThreadLocal则是命令式编程:上下文允许将上下文数据绑定到特定执行。对于反应式编程,这是一个订阅。通过Reactor的Context,Spring可以将事务状态以及所有资源和同步绑定到特定的Subscription。现在,所有使用Project Reactor的反应式代码都可以参与反应式事务。返回标量值并想要访问事务详细信息的代码必须进行重写,以使用反应性类型来参与事务。否则,上下文不可用。
反应式交易管理

从Spring Framework 5.2 M2开始,Spring通过ReactiveTransactionManager SPI支持反应式事务管理。

ReactiveTransactionManager是用于使用事务资源的反应性和非阻塞式集成的事务管理抽象。它是返回发布者类型的反应式@Transactional方法以及使用TransactionalOperator的程序化事务管理的基础。

Spring Security是一个用于保护Java应用程序的框架,它提供了身份验证、授权和其他安全功能。在传统的Spring应用程序中,Spring Security使用Servlet API来实现安全性。然而,随着响应式编程的兴起,Spring Security也提供了对响应式应用程序的支持,称为Reactive Spring Security。 Reactive Spring Security基于Spring WebFlux框架,它使用响应式编程模型来处理请求和响应。与传统的Servlet API不同,Reactive Spring Security使用了一种基于反应式流的安全性模型,可以处理大量并发请求,并具有更好的性能和可伸缩性。 Reactive Spring Security的原理是通过使用WebFilter来拦截请求,并使用SecurityContext来管理用户的身份验证和授权信息。它还提供了一系列的SecurityWebFilterChain,用于定义不同URL路径的安全规则和访问控制。 下面是一个演示Reactive Spring Security的例子: ```java @Configuration @EnableWebFluxSecurity public class SecurityConfig { @Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { return http .authorizeExchange() .pathMatchers("/public/**").permitAll() .pathMatchers("/private/**").authenticated() .and() .build(); } @Bean public ReactiveUserDetailsService userDetailsService() { UserDetails user = User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build(); return new MapReactiveUserDetailsService(user); } } ``` 上述代码配置了一个简单的Reactive Spring Security应用程序。它定义了两个URL路径:/public/**和/private/**。/public/**路径允许所有用户访问,而/private/**路径需要进行身份验证。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值