Add reactive transaction support SPI
This commit adds SPI interfaces to support reactive transactions through spring-tx with optional dependencies to Project Reactor and supportive implementations for TransactionalOperator and AbstractReactiveTransactionManager.
This commit is contained in:
committed by
Juergen Hoeller
parent
cee7d09e40
commit
beea83b9d2
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.transaction.reactive;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.transaction.CannotCreateTransactionException;
|
||||
import org.springframework.transaction.ReactiveTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
|
||||
/**
|
||||
* Test implementation of a {@link ReactiveTransactionManager}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
class ReactiveTestTransactionManager extends AbstractReactiveTransactionManager {
|
||||
|
||||
private static final Object TRANSACTION = "transaction";
|
||||
|
||||
private final boolean existingTransaction;
|
||||
|
||||
private final boolean canCreateTransaction;
|
||||
|
||||
protected boolean begin = false;
|
||||
|
||||
protected boolean commit = false;
|
||||
|
||||
protected boolean rollback = false;
|
||||
|
||||
protected boolean rollbackOnly = false;
|
||||
|
||||
|
||||
ReactiveTestTransactionManager(boolean existingTransaction, boolean canCreateTransaction) {
|
||||
this.existingTransaction = existingTransaction;
|
||||
this.canCreateTransaction = canCreateTransaction;
|
||||
setTransactionSynchronization(SYNCHRONIZATION_NEVER);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Object doGetTransaction(ReactiveTransactionSynchronizationManager synchronizationManager) {
|
||||
return TRANSACTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isExistingTransaction(Object transaction) {
|
||||
return existingTransaction;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Mono<Void> doBegin(ReactiveTransactionSynchronizationManager synchronizationManager, Object transaction, TransactionDefinition definition) {
|
||||
if (!TRANSACTION.equals(transaction)) {
|
||||
return Mono.error(new IllegalArgumentException("Not the same transaction object"));
|
||||
}
|
||||
if (!this.canCreateTransaction) {
|
||||
return Mono.error(new CannotCreateTransactionException("Cannot create transaction"));
|
||||
}
|
||||
return Mono.fromRunnable(() -> this.begin = true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Mono<Void> doCommit(ReactiveTransactionSynchronizationManager synchronizationManager, DefaultReactiveTransactionStatus status) {
|
||||
if (!TRANSACTION.equals(status.getTransaction())) {
|
||||
return Mono.error(new IllegalArgumentException("Not the same transaction object"));
|
||||
}
|
||||
return Mono.fromRunnable(() -> this.commit = true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Mono<Void> doRollback(ReactiveTransactionSynchronizationManager synchronizationManager, DefaultReactiveTransactionStatus status) {
|
||||
if (!TRANSACTION.equals(status.getTransaction())) {
|
||||
return Mono.error(new IllegalArgumentException("Not the same transaction object"));
|
||||
}
|
||||
return Mono.fromRunnable(() -> this.rollback = true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Mono<Void> doSetRollbackOnly(ReactiveTransactionSynchronizationManager synchronizationManager, DefaultReactiveTransactionStatus status) {
|
||||
if (!TRANSACTION.equals(status.getTransaction())) {
|
||||
return Mono.error(new IllegalArgumentException("Not the same transaction object"));
|
||||
}
|
||||
return Mono.fromRunnable(() -> this.rollbackOnly = true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.transaction.reactive;
|
||||
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.transaction.IllegalTransactionStateException;
|
||||
import org.springframework.transaction.ReactiveTransactionManager;
|
||||
import org.springframework.transaction.ReactiveTransactionStatus;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.support.DefaultTransactionDefinition;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Unit tests for transactional support through {@link ReactiveTestTransactionManager}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class ReactiveTransactionSupportUnitTests {
|
||||
|
||||
@Test
|
||||
public void noExistingTransaction() {
|
||||
ReactiveTransactionManager tm = new ReactiveTestTransactionManager(false, true);
|
||||
|
||||
tm.getTransaction(new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_SUPPORTS))
|
||||
.subscriberContext(TransactionContextManager.createTransactionContext()).cast(DefaultReactiveTransactionStatus.class)
|
||||
.as(StepVerifier::create).consumeNextWith(actual -> {
|
||||
assertFalse(actual.hasTransaction());
|
||||
}).verifyComplete();
|
||||
|
||||
tm.getTransaction(new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRED))
|
||||
.cast(DefaultReactiveTransactionStatus.class).subscriberContext(TransactionContextManager.createTransactionContext())
|
||||
.as(StepVerifier::create).consumeNextWith(actual -> {
|
||||
assertTrue(actual.hasTransaction());
|
||||
assertTrue(actual.isNewTransaction());
|
||||
}).verifyComplete();
|
||||
|
||||
tm.getTransaction(new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_MANDATORY))
|
||||
.subscriberContext(TransactionContextManager.createTransactionContext()).cast(DefaultReactiveTransactionStatus.class)
|
||||
.as(StepVerifier::create).expectError(IllegalTransactionStateException.class).verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void existingTransaction() {
|
||||
ReactiveTransactionManager tm = new ReactiveTestTransactionManager(true, true);
|
||||
|
||||
tm.getTransaction(new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_SUPPORTS))
|
||||
.subscriberContext(TransactionContextManager.createTransactionContext()).cast(DefaultReactiveTransactionStatus.class)
|
||||
.as(StepVerifier::create).consumeNextWith(actual -> {
|
||||
assertNotNull(actual.getTransaction());
|
||||
assertFalse(actual.isNewTransaction());
|
||||
}).verifyComplete();
|
||||
|
||||
tm.getTransaction(new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRED))
|
||||
.subscriberContext(TransactionContextManager.createTransactionContext()).cast(DefaultReactiveTransactionStatus.class)
|
||||
.as(StepVerifier::create).consumeNextWith(actual -> {
|
||||
assertNotNull(actual.getTransaction());
|
||||
assertFalse(actual.isNewTransaction());
|
||||
}).verifyComplete();
|
||||
|
||||
tm.getTransaction(new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_MANDATORY))
|
||||
.subscriberContext(TransactionContextManager.createTransactionContext()).cast(DefaultReactiveTransactionStatus.class)
|
||||
.as(StepVerifier::create).consumeNextWith(actual -> {
|
||||
assertNotNull(actual.getTransaction());
|
||||
assertFalse(actual.isNewTransaction());
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void commitWithoutExistingTransaction() {
|
||||
ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(false, true);
|
||||
tm.getTransaction(null).flatMap(tm::commit).subscriberContext(TransactionContextManager.createTransactionContext())
|
||||
.as(StepVerifier::create).verifyComplete();
|
||||
|
||||
assertHasBegan(tm);
|
||||
assertHasCommitted(tm);
|
||||
assertHasNoRollback(tm);
|
||||
assertHasNotSetRollbackOnly(tm);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rollbackWithoutExistingTransaction() {
|
||||
ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(false, true);
|
||||
tm.getTransaction(null).flatMap(tm::rollback)
|
||||
.subscriberContext(TransactionContextManager.createTransactionContext()).as(StepVerifier::create)
|
||||
.verifyComplete();
|
||||
|
||||
assertHasBegan(tm);
|
||||
assertHasNotCommitted(tm);
|
||||
assertHasRolledBack(tm);
|
||||
assertHasNotSetRollbackOnly(tm);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rollbackOnlyWithoutExistingTransaction() {
|
||||
ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(false, true);
|
||||
tm.getTransaction(null).doOnNext(ReactiveTransactionStatus::setRollbackOnly).flatMap(tm::commit)
|
||||
.subscriberContext(TransactionContextManager.createTransactionContext()).as(StepVerifier::create)
|
||||
.verifyComplete();
|
||||
|
||||
assertHasBegan(tm);
|
||||
assertHasNotCommitted(tm);
|
||||
assertHasRolledBack(tm);
|
||||
assertHasNotSetRollbackOnly(tm);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void commitWithExistingTransaction() {
|
||||
ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(true, true);
|
||||
tm.getTransaction(null).flatMap(tm::commit).subscriberContext(TransactionContextManager.createTransactionContext())
|
||||
.as(StepVerifier::create).verifyComplete();
|
||||
|
||||
assertHasNotBegan(tm);
|
||||
assertHasNotCommitted(tm);
|
||||
assertHasNoRollback(tm);
|
||||
assertHasNotSetRollbackOnly(tm);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rollbackWithExistingTransaction() {
|
||||
ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(true, true);
|
||||
tm.getTransaction(null).flatMap(tm::rollback)
|
||||
.subscriberContext(TransactionContextManager.createTransactionContext()).as(StepVerifier::create)
|
||||
.verifyComplete();
|
||||
|
||||
assertHasNotBegan(tm);
|
||||
assertHasNotCommitted(tm);
|
||||
assertHasNoRollback(tm);
|
||||
assertHasSetRollbackOnly(tm);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rollbackOnlyWithExistingTransaction() {
|
||||
ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(true, true);
|
||||
tm.getTransaction(null).doOnNext(ReactiveTransactionStatus::setRollbackOnly).flatMap(tm::commit)
|
||||
.subscriberContext(TransactionContextManager.createTransactionContext()).as(StepVerifier::create)
|
||||
.verifyComplete();
|
||||
|
||||
assertHasNotBegan(tm);
|
||||
assertHasNotCommitted(tm);
|
||||
assertHasNoRollback(tm);
|
||||
assertHasSetRollbackOnly(tm);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transactionTemplate() {
|
||||
ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(false, true);
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm);
|
||||
|
||||
Flux.just("Walter").as(operator::transactional)
|
||||
.as(StepVerifier::create)
|
||||
.expectNextCount(1)
|
||||
.verifyComplete();
|
||||
|
||||
assertHasBegan(tm);
|
||||
assertHasCommitted(tm);
|
||||
assertHasNoRollback(tm);
|
||||
assertHasNotSetRollbackOnly(tm);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transactionTemplateWithException() {
|
||||
ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(false, true);
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm);
|
||||
RuntimeException ex = new RuntimeException("Some application exception");
|
||||
|
||||
Mono.error(ex).as(operator::transactional)
|
||||
.as(StepVerifier::create)
|
||||
.expectError(RuntimeException.class)
|
||||
.verify();
|
||||
|
||||
assertHasBegan(tm);
|
||||
assertHasNotCommitted(tm);
|
||||
assertHasRolledBack(tm);
|
||||
assertHasNotSetRollbackOnly(tm);
|
||||
}
|
||||
|
||||
private void assertHasBegan(ReactiveTestTransactionManager actual) {
|
||||
assertTrue("Expected <ReactiveTransactionManager.begin()> but was <begin()> was not invoked", actual.begin);
|
||||
}
|
||||
|
||||
private void assertHasNotBegan(ReactiveTestTransactionManager actual) {
|
||||
assertFalse("Expected to not call <ReactiveTransactionManager.begin()> but was <begin()> was called", actual.begin);
|
||||
}
|
||||
|
||||
private void assertHasCommitted(ReactiveTestTransactionManager actual) {
|
||||
assertTrue("Expected <ReactiveTransactionManager.commit()> but was <commit()> was not invoked", actual.commit);
|
||||
}
|
||||
|
||||
private void assertHasNotCommitted(ReactiveTestTransactionManager actual) {
|
||||
assertFalse("Expected to not call <ReactiveTransactionManager.commit()> but was <commit()> was called", actual.commit);
|
||||
}
|
||||
|
||||
private void assertHasRolledBack(ReactiveTestTransactionManager actual) {
|
||||
assertTrue("Expected <ReactiveTransactionManager.rollback()> but was <rollback()> was not invoked", actual.rollback);
|
||||
}
|
||||
|
||||
private void assertHasNoRollback(ReactiveTestTransactionManager actual) {
|
||||
assertFalse("Expected to not call <ReactiveTransactionManager.rollback()> but was <rollback()> was called", actual.rollback);
|
||||
}
|
||||
|
||||
private void assertHasSetRollbackOnly(ReactiveTestTransactionManager actual) {
|
||||
assertTrue("Expected <ReactiveTransactionManager.setRollbackOnly()> but was <setRollbackOnly()> was not invoked", actual.rollbackOnly);
|
||||
}
|
||||
|
||||
private void assertHasNotSetRollbackOnly(ReactiveTestTransactionManager actual) {
|
||||
assertFalse("Expected to not call <ReactiveTransactionManager.setRollbackOnly()> but was <setRollbackOnly()> was called", actual.rollbackOnly);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.transaction.reactive;
|
||||
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Tests for {@link TransactionalOperator}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class TransactionalOperatorTests {
|
||||
|
||||
ReactiveTestTransactionManager tm = new ReactiveTestTransactionManager(false, true);
|
||||
|
||||
@Test
|
||||
public void commitWithMono() {
|
||||
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm);
|
||||
|
||||
Mono.just(true).as(operator::transactional)
|
||||
.as(StepVerifier::create)
|
||||
.expectNext(true)
|
||||
.verifyComplete();
|
||||
|
||||
assertTrue(tm.commit);
|
||||
assertFalse(tm.rollback);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rollbackWithMono() {
|
||||
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm);
|
||||
|
||||
Mono.error(new IllegalStateException()).as(operator::transactional)
|
||||
.as(StepVerifier::create)
|
||||
.verifyError(IllegalStateException.class);
|
||||
|
||||
assertFalse(tm.commit);
|
||||
assertTrue(tm.rollback);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void commitWithFlux() {
|
||||
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm);
|
||||
|
||||
Flux.just(true).as(operator::transactional)
|
||||
.as(StepVerifier::create)
|
||||
.expectNext(true)
|
||||
.verifyComplete();
|
||||
|
||||
assertTrue(tm.commit);
|
||||
assertFalse(tm.rollback);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rollbackWithFlux() {
|
||||
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm);
|
||||
|
||||
Flux.error(new IllegalStateException()).as(operator::transactional)
|
||||
.as(StepVerifier::create)
|
||||
.verifyError(IllegalStateException.class);
|
||||
|
||||
assertFalse(tm.commit);
|
||||
assertTrue(tm.rollback);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user