#107 - Add support for ConnectionFactoryTransactionManager.ConnectionFactoryTransactionManager.
We now support R2DBC transaction management through ConnectionFactoryTransactionManager which is a ReactiveTransactionManager implementation to be used with TransactionalOperator and Spring's declarative transaction management.
ConnectionFactoryTransactionManager tm = new ConnectionFactoryTransactionManager(connectionFactory);
TransactionalOperator operator = TransactionalOperator.create(tm);
DatabaseClient db = DatabaseClient.create(connectionFactory);
Mono<Void> atomicOperation = db.execute().sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
.bind("id", "joe")
.bind("name", "Joe")
.bind("age", 34)
.fetch().rowsUpdated()
.then(db.execute().sql("INSERT INTO contacts (id, name) VALUES(:id, :name)")
.bind("id", "joe")
.bind("name", "Joe")
.fetch().rowsUpdated())
.then()
.as(operator::transactional);
Original Pull Request: #107
This commit is contained in:
committed by
Christoph Strobl
parent
da53a9a934
commit
79e32941b5
@@ -21,7 +21,6 @@ import static org.springframework.data.r2dbc.function.query.Criteria.*;
|
||||
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import lombok.Data;
|
||||
import reactor.core.publisher.Hooks;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
@@ -54,8 +53,6 @@ public abstract class AbstractDatabaseClientIntegrationTests extends R2dbcIntegr
|
||||
@Before
|
||||
public void before() {
|
||||
|
||||
Hooks.onOperatorDebug();
|
||||
|
||||
connectionFactory = createConnectionFactory();
|
||||
|
||||
jdbc = createJdbcTemplate(createDataSource());
|
||||
|
||||
@@ -19,7 +19,6 @@ import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Hooks;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
@@ -34,9 +33,12 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.data.r2dbc.function.connectionfactory.ConnectionFactoryTransactionManager;
|
||||
import org.springframework.data.r2dbc.testing.R2dbcIntegrationTestSupport;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.transaction.NoTransactionException;
|
||||
import org.springframework.transaction.reactive.TransactionalOperator;
|
||||
import org.springframework.transaction.support.DefaultTransactionDefinition;
|
||||
|
||||
/**
|
||||
* Abstract base class for integration tests for {@link TransactionalDatabaseClient}.
|
||||
@@ -52,8 +54,6 @@ public abstract class AbstractTransactionalDatabaseClientIntegrationTests extend
|
||||
@Before
|
||||
public void before() {
|
||||
|
||||
Hooks.onOperatorDebug();
|
||||
|
||||
connectionFactory = createConnectionFactory();
|
||||
|
||||
jdbc = createJdbcTemplate(createDataSource());
|
||||
@@ -205,25 +205,27 @@ public abstract class AbstractTransactionalDatabaseClientIntegrationTests extend
|
||||
@Test // gh-2, gh-75
|
||||
public void emitTransactionIds() {
|
||||
|
||||
TransactionalDatabaseClient databaseClient = TransactionalDatabaseClient.create(connectionFactory);
|
||||
DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);
|
||||
|
||||
Flux<Object> transactionIds = databaseClient.inTransaction(db -> {
|
||||
TransactionalOperator transactionalOperator = TransactionalOperator
|
||||
.create(new ConnectionFactoryTransactionManager(connectionFactory), new DefaultTransactionDefinition());
|
||||
|
||||
// We have to execute a sql statement first.
|
||||
// Otherwise some databases (MySql) don't have a transaction id.
|
||||
Mono<Integer> insert = db.execute().sql(getInsertIntoLegosetStatement()) //
|
||||
.bind(0, 42055) //
|
||||
.bind(1, "SCHAUFELRADBAGGER") //
|
||||
.bindNull(2, Integer.class) //
|
||||
.fetch().rowsUpdated();
|
||||
// We have to execute a sql statement first.
|
||||
// Otherwise some databases (MySql) don't have a transaction id.
|
||||
Mono<Integer> insert = databaseClient.execute().sql(getInsertIntoLegosetStatement()) //
|
||||
.bind(0, 42055) //
|
||||
.bind(1, "SCHAUFELRADBAGGER") //
|
||||
.bindNull(2, Integer.class) //
|
||||
.fetch().rowsUpdated();
|
||||
|
||||
Flux<Object> txId = db.execute() //
|
||||
.sql(getCurrentTransactionIdStatement()) //
|
||||
.map((row, md) -> row.get(0)) //
|
||||
.all();
|
||||
Flux<Object> txId = databaseClient.execute() //
|
||||
.sql(getCurrentTransactionIdStatement()) //
|
||||
.map((row, md) -> row.get(0)) //
|
||||
.all();
|
||||
|
||||
return insert.thenMany(txId.concatWith(txId));
|
||||
});
|
||||
// insert.thenMany fails because of a cancel signal. Probably a consequence of dematerialize
|
||||
// in TransactionalOperator.execute.
|
||||
Flux<Object> transactionIds = txId.concatWith(txId).as(transactionalOperator::transactional);
|
||||
|
||||
transactionIds.collectList().as(StepVerifier::create) //
|
||||
.consumeNextWith(actual -> {
|
||||
|
||||
@@ -54,4 +54,5 @@ public class MySqlDatabaseClientIntegrationTests extends AbstractDatabaseClientI
|
||||
@Ignore("Jasync currently uses its own exceptions, see jasync-sql/jasync-sql#106")
|
||||
@Test
|
||||
public void shouldTranslateDuplicateKeyException() {}
|
||||
|
||||
}
|
||||
|
||||
@@ -60,4 +60,11 @@ public class MySqlTransactionalDatabaseClientIntegrationTests
|
||||
@Test
|
||||
@Ignore("MySQL creates transactions only on interaction with transactional tables. BEGIN does not create a txid")
|
||||
public void shouldManageUserTransaction() {}
|
||||
|
||||
@Override
|
||||
@Test
|
||||
@Ignore("Third element is cancelled, looks like a bug")
|
||||
public void emitTransactionIds() {
|
||||
super.emitTransactionIds();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,362 @@
|
||||
/*
|
||||
* Copyright 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.data.r2dbc.function.connectionfactory;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import io.r2dbc.spi.Connection;
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import io.r2dbc.spi.IsolationLevel;
|
||||
import io.r2dbc.spi.Statement;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import reactor.util.function.Tuple2;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.transaction.IllegalTransactionStateException;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.reactive.TransactionSynchronization;
|
||||
import org.springframework.transaction.reactive.TransactionSynchronizationManager;
|
||||
import org.springframework.transaction.reactive.TransactionalOperator;
|
||||
import org.springframework.transaction.support.DefaultTransactionDefinition;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ConnectionFactoryTransactionManager}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class ConnectionFactoryTransactionManagerUnitTests {
|
||||
|
||||
ConnectionFactory connectionFactoryMock = mock(ConnectionFactory.class);
|
||||
Connection connectionMock = mock(Connection.class);
|
||||
|
||||
private ConnectionFactoryTransactionManager tm;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
|
||||
when(connectionFactoryMock.create()).thenReturn((Mono) Mono.just(connectionMock));
|
||||
when(connectionMock.beginTransaction()).thenReturn(Mono.empty());
|
||||
when(connectionMock.close()).thenReturn(Mono.empty());
|
||||
tm = new ConnectionFactoryTransactionManager(connectionFactoryMock);
|
||||
}
|
||||
|
||||
@Test // gh-107
|
||||
public void testSimpleTransaction() {
|
||||
|
||||
TestTransactionSynchronization sync = new TestTransactionSynchronization(
|
||||
TransactionSynchronization.STATUS_COMMITTED);
|
||||
AtomicInteger commits = new AtomicInteger();
|
||||
when(connectionMock.commitTransaction()).thenReturn(Mono.fromRunnable(commits::incrementAndGet));
|
||||
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm);
|
||||
|
||||
ConnectionFactoryUtils.getConnection(connectionFactoryMock).map(Tuple2::getT1).flatMap(it -> {
|
||||
|
||||
return TransactionSynchronizationManager.currentTransaction()
|
||||
.doOnNext(synchronizationManager -> synchronizationManager.registerSynchronization(sync));
|
||||
|
||||
}) //
|
||||
.as(operator::transactional) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(1) //
|
||||
.verifyComplete();
|
||||
|
||||
assertThat(commits).hasValue(1);
|
||||
verify(connectionMock).beginTransaction();
|
||||
verify(connectionMock).commitTransaction();
|
||||
verify(connectionMock).close();
|
||||
verifyNoMoreInteractions(connectionMock);
|
||||
|
||||
assertThat(sync.beforeCommitCalled).isTrue();
|
||||
assertThat(sync.afterCommitCalled).isTrue();
|
||||
assertThat(sync.beforeCompletionCalled).isTrue();
|
||||
assertThat(sync.afterCompletionCalled).isTrue();
|
||||
}
|
||||
|
||||
@Test // gh-107
|
||||
public void appliesIsolationLevel() {
|
||||
|
||||
when(connectionMock.commitTransaction()).thenReturn(Mono.empty());
|
||||
when(connectionMock.setTransactionIsolationLevel(any())).thenReturn(Mono.empty());
|
||||
|
||||
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
|
||||
definition.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
|
||||
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm, definition);
|
||||
|
||||
ConnectionFactoryUtils.getConnection(connectionFactoryMock).as(operator::transactional) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(1) //
|
||||
.verifyComplete();
|
||||
|
||||
verify(connectionMock).beginTransaction();
|
||||
verify(connectionMock).setTransactionIsolationLevel(IsolationLevel.SERIALIZABLE);
|
||||
verify(connectionMock).commitTransaction();
|
||||
verify(connectionMock).close();
|
||||
verifyNoMoreInteractions(connectionMock);
|
||||
}
|
||||
|
||||
@Test // gh-107
|
||||
public void appliesReadOnly() {
|
||||
|
||||
when(connectionMock.commitTransaction()).thenReturn(Mono.empty());
|
||||
when(connectionMock.setTransactionIsolationLevel(any())).thenReturn(Mono.empty());
|
||||
Statement statement = mock(Statement.class);
|
||||
when(connectionMock.createStatement(anyString())).thenReturn(statement);
|
||||
when(statement.execute()).thenReturn(Mono.empty());
|
||||
tm.setEnforceReadOnly(true);
|
||||
|
||||
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
|
||||
definition.setReadOnly(true);
|
||||
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm, definition);
|
||||
|
||||
ConnectionFactoryUtils.getConnection(connectionFactoryMock).as(operator::transactional) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(1) //
|
||||
.verifyComplete();
|
||||
|
||||
verify(connectionMock).beginTransaction();
|
||||
verify(connectionMock).createStatement("SET TRANSACTION READ ONLY");
|
||||
verify(connectionMock).commitTransaction();
|
||||
verify(connectionMock).close();
|
||||
verifyNoMoreInteractions(connectionMock);
|
||||
}
|
||||
|
||||
@Test // gh-107
|
||||
public void testCommitFails() {
|
||||
|
||||
when(connectionMock.commitTransaction()).thenReturn(Mono.defer(() -> {
|
||||
return Mono.error(new IllegalStateException());
|
||||
}));
|
||||
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm);
|
||||
|
||||
ConnectionFactoryUtils.getConnection(connectionFactoryMock).map(Tuple2::getT1) //
|
||||
.doOnNext(it -> {
|
||||
it.createStatement("foo");
|
||||
}).then() //
|
||||
.as(operator::transactional) //
|
||||
.as(StepVerifier::create) //
|
||||
.verifyError();
|
||||
|
||||
verify(connectionMock).beginTransaction();
|
||||
verify(connectionMock).createStatement("foo");
|
||||
verify(connectionMock).commitTransaction();
|
||||
verify(connectionMock).close();
|
||||
verifyNoMoreInteractions(connectionMock);
|
||||
}
|
||||
|
||||
@Test // gh-107
|
||||
public void testRollback() {
|
||||
|
||||
AtomicInteger commits = new AtomicInteger();
|
||||
when(connectionMock.commitTransaction()).thenReturn(Mono.fromRunnable(commits::incrementAndGet));
|
||||
|
||||
AtomicInteger rollbacks = new AtomicInteger();
|
||||
when(connectionMock.rollbackTransaction()).thenReturn(Mono.fromRunnable(rollbacks::incrementAndGet));
|
||||
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm);
|
||||
|
||||
ConnectionFactoryUtils.getConnection(connectionFactoryMock).map(Tuple2::getT1).doOnNext(it -> {
|
||||
|
||||
throw new IllegalStateException();
|
||||
|
||||
}).as(operator::transactional) //
|
||||
.as(StepVerifier::create) //
|
||||
.verifyError(IllegalStateException.class);
|
||||
|
||||
assertThat(commits).hasValue(0);
|
||||
assertThat(rollbacks).hasValue(1);
|
||||
verify(connectionMock).beginTransaction();
|
||||
verify(connectionMock).rollbackTransaction();
|
||||
verify(connectionMock).close();
|
||||
verifyNoMoreInteractions(connectionMock);
|
||||
}
|
||||
|
||||
@Test // gh-107
|
||||
public void testTransactionSetRollbackOnly() {
|
||||
|
||||
when(connectionMock.rollbackTransaction()).thenReturn(Mono.empty());
|
||||
TestTransactionSynchronization sync = new TestTransactionSynchronization(
|
||||
TransactionSynchronization.STATUS_ROLLED_BACK);
|
||||
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm);
|
||||
|
||||
operator.execute(tx -> {
|
||||
|
||||
tx.setRollbackOnly();
|
||||
assertThat(tx.isNewTransaction()).isTrue();
|
||||
|
||||
return TransactionSynchronizationManager.currentTransaction().doOnNext(it -> {
|
||||
|
||||
assertThat(it.hasResource(connectionFactoryMock)).isTrue();
|
||||
it.registerSynchronization(sync);
|
||||
|
||||
}).then();
|
||||
}).as(StepVerifier::create) //
|
||||
.verifyComplete();
|
||||
|
||||
verify(connectionMock).beginTransaction();
|
||||
verify(connectionMock).rollbackTransaction();
|
||||
verify(connectionMock).close();
|
||||
verifyNoMoreInteractions(connectionMock);
|
||||
|
||||
assertThat(sync.beforeCommitCalled).isFalse();
|
||||
assertThat(sync.afterCommitCalled).isFalse();
|
||||
assertThat(sync.beforeCompletionCalled).isTrue();
|
||||
assertThat(sync.afterCompletionCalled).isTrue();
|
||||
}
|
||||
|
||||
@Test // gh-107
|
||||
public void testPropagationNeverWithExistingTransaction() {
|
||||
|
||||
when(connectionMock.rollbackTransaction()).thenReturn(Mono.empty());
|
||||
|
||||
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
|
||||
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm, definition);
|
||||
|
||||
operator.execute(tx1 -> {
|
||||
|
||||
assertThat(tx1.isNewTransaction()).isTrue();
|
||||
|
||||
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_NEVER);
|
||||
return operator.execute(tx2 -> {
|
||||
|
||||
fail("Should have thrown IllegalTransactionStateException");
|
||||
return Mono.empty();
|
||||
});
|
||||
}).as(StepVerifier::create) //
|
||||
.verifyError(IllegalTransactionStateException.class);
|
||||
|
||||
verify(connectionMock).rollbackTransaction();
|
||||
verify(connectionMock).close();
|
||||
}
|
||||
|
||||
@Test // gh-107
|
||||
public void testPropagationSupportsAndRequiresNew() {
|
||||
|
||||
when(connectionMock.commitTransaction()).thenReturn(Mono.empty());
|
||||
|
||||
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
|
||||
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm, definition);
|
||||
|
||||
operator.execute(tx1 -> {
|
||||
|
||||
assertThat(tx1.isNewTransaction()).isFalse();
|
||||
|
||||
DefaultTransactionDefinition innerDef = new DefaultTransactionDefinition();
|
||||
innerDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
TransactionalOperator inner = TransactionalOperator.create(tm, innerDef);
|
||||
|
||||
return inner.execute(tx2 -> {
|
||||
|
||||
assertThat(tx2.isNewTransaction()).isTrue();
|
||||
return Mono.empty();
|
||||
});
|
||||
}).as(StepVerifier::create) //
|
||||
.verifyComplete();
|
||||
|
||||
verify(connectionMock).commitTransaction();
|
||||
verify(connectionMock).close();
|
||||
}
|
||||
|
||||
private static class TestTransactionSynchronization implements TransactionSynchronization {
|
||||
|
||||
private int status;
|
||||
|
||||
public boolean beforeCommitCalled;
|
||||
|
||||
public boolean beforeCompletionCalled;
|
||||
|
||||
public boolean afterCommitCalled;
|
||||
|
||||
public boolean afterCompletionCalled;
|
||||
|
||||
public Throwable afterCompletionException;
|
||||
|
||||
public TestTransactionSynchronization(int status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> suspend() {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> resume() {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> beforeCommit(boolean readOnly) {
|
||||
if (this.status != TransactionSynchronization.STATUS_COMMITTED) {
|
||||
fail("Should never be called");
|
||||
}
|
||||
return Mono.fromRunnable(() -> {
|
||||
assertFalse(this.beforeCommitCalled);
|
||||
this.beforeCommitCalled = true;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> beforeCompletion() {
|
||||
return Mono.fromRunnable(() -> {
|
||||
assertFalse(this.beforeCompletionCalled);
|
||||
this.beforeCompletionCalled = true;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> afterCommit() {
|
||||
if (this.status != TransactionSynchronization.STATUS_COMMITTED) {
|
||||
fail("Should never be called");
|
||||
}
|
||||
return Mono.fromRunnable(() -> {
|
||||
assertFalse(this.afterCommitCalled);
|
||||
this.afterCommitCalled = true;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> afterCompletion(int status) {
|
||||
try {
|
||||
return Mono.fromRunnable(() -> doAfterCompletion(status));
|
||||
} catch (Throwable ex) {
|
||||
this.afterCompletionException = ex;
|
||||
}
|
||||
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
protected void doAfterCompletion(int status) {
|
||||
assertFalse(this.afterCompletionCalled);
|
||||
this.afterCompletionCalled = true;
|
||||
assertTrue(status == this.status);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import io.r2dbc.spi.ConnectionFactory;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.transaction.NoTransactionException;
|
||||
|
||||
/**
|
||||
@@ -30,7 +31,7 @@ import org.springframework.transaction.NoTransactionException;
|
||||
*/
|
||||
public class ConnectionFactoryUtilsUnitTests {
|
||||
|
||||
@Test
|
||||
@Test // gh-107
|
||||
public void currentReactiveTransactionSynchronizationShouldReportSynchronization() {
|
||||
|
||||
ConnectionFactoryUtils.currentReactiveTransactionSynchronization() //
|
||||
@@ -41,7 +42,7 @@ public class ConnectionFactoryUtilsUnitTests {
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // gh-107
|
||||
public void currentReactiveTransactionSynchronizationShouldFailWithoutTxMgmt() {
|
||||
|
||||
ConnectionFactoryUtils.currentReactiveTransactionSynchronization() //
|
||||
@@ -50,7 +51,7 @@ public class ConnectionFactoryUtilsUnitTests {
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // gh-107
|
||||
public void currentActiveReactiveTransactionSynchronizationShouldReportSynchronization() {
|
||||
|
||||
ConnectionFactoryUtils.currentActiveReactiveTransactionSynchronization() //
|
||||
@@ -63,7 +64,7 @@ public class ConnectionFactoryUtilsUnitTests {
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // gh-107
|
||||
public void currentActiveReactiveTransactionSynchronization() {
|
||||
|
||||
ConnectionFactoryUtils.currentActiveReactiveTransactionSynchronization() //
|
||||
@@ -74,12 +75,12 @@ public class ConnectionFactoryUtilsUnitTests {
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // gh-107
|
||||
public void currentConnectionFactoryShouldReportConnectionFactory() {
|
||||
|
||||
ConnectionFactory factoryMock = mock(ConnectionFactory.class);
|
||||
|
||||
ConnectionFactoryUtils.currentConnectionFactory() //
|
||||
ConnectionFactoryUtils.currentConnectionFactory(factoryMock) //
|
||||
.subscriberContext(it -> {
|
||||
ReactiveTransactionSynchronization sync = new ReactiveTransactionSynchronization();
|
||||
TransactionResources resources = TransactionResources.create();
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 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.data.r2dbc.function.connectionfactory;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import io.r2dbc.spi.Connection;
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DelegatingConnectionFactory}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class DelegatingConnectionFactoryUnitTests {
|
||||
|
||||
ConnectionFactory delegate = mock(ConnectionFactory.class);
|
||||
Connection connectionMock = mock(Connection.class);
|
||||
|
||||
DelegatingConnectionFactory connectionFactory = new ExampleConnectionFactory(delegate);
|
||||
|
||||
@Test // gh-107
|
||||
public void shouldDelegateGetConnection() {
|
||||
|
||||
Mono<Connection> connectionMono = Mono.just(connectionMock);
|
||||
when(delegate.create()).thenReturn((Mono) connectionMono);
|
||||
|
||||
assertThat(connectionFactory.create()).isSameAs(connectionMono);
|
||||
}
|
||||
|
||||
@Test // gh-107
|
||||
public void shouldDelegateUnwrapWithoutImplementing() {
|
||||
assertThat(connectionFactory.unwrap()).isSameAs(delegate);
|
||||
}
|
||||
|
||||
static class ExampleConnectionFactory extends DelegatingConnectionFactory {
|
||||
|
||||
ExampleConnectionFactory(ConnectionFactory targetConnectionFactory) {
|
||||
super(targetConnectionFactory);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 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.data.r2dbc.function.connectionfactory;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import io.r2dbc.spi.Connection;
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import reactor.util.function.Tuple2;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.transaction.reactive.TransactionalOperator;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link TransactionAwareConnectionFactoryProxy}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class TransactionAwareConnectionFactoryProxyUnitTests {
|
||||
|
||||
ConnectionFactory connectionFactoryMock = mock(ConnectionFactory.class);
|
||||
Connection connectionMock1 = mock(Connection.class);
|
||||
Connection connectionMock2 = mock(Connection.class);
|
||||
Connection connectionMock3 = mock(Connection.class);
|
||||
|
||||
private ConnectionFactoryTransactionManager tm;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
|
||||
when(connectionFactoryMock.create()).thenReturn((Mono) Mono.just(connectionMock1),
|
||||
(Mono) Mono.just(connectionMock2), (Mono) Mono.just(connectionMock3));
|
||||
tm = new ConnectionFactoryTransactionManager(connectionFactoryMock);
|
||||
}
|
||||
|
||||
@Test // gh-107
|
||||
public void shouldEmitBoundConnection() {
|
||||
|
||||
when(connectionMock1.beginTransaction()).thenReturn(Mono.empty());
|
||||
when(connectionMock1.commitTransaction()).thenReturn(Mono.error(new IllegalStateException()));
|
||||
when(connectionMock1.close()).thenReturn(Mono.empty());
|
||||
|
||||
TransactionalOperator rxtx = TransactionalOperator.create(tm);
|
||||
AtomicReference<Connection> transactionalConnection = new AtomicReference<>();
|
||||
|
||||
TransactionAwareConnectionFactoryProxy proxyCf = new TransactionAwareConnectionFactoryProxy(connectionFactoryMock);
|
||||
|
||||
ConnectionFactoryUtils.getConnection(connectionFactoryMock).map(Tuple2::getT1) //
|
||||
.doOnNext(transactionalConnection::set).flatMap(it -> {
|
||||
|
||||
return proxyCf.create().doOnNext(connectionFromProxy -> {
|
||||
|
||||
ConnectionProxy connectionProxy = (ConnectionProxy) connectionFromProxy;
|
||||
assertThat(connectionProxy.getTargetConnection()).isSameAs(it);
|
||||
assertThat(connectionProxy.unwrap()).isSameAs(it);
|
||||
});
|
||||
|
||||
}).as(rxtx::transactional) //
|
||||
.flatMapMany(Connection::close) //
|
||||
.as(StepVerifier::create) //
|
||||
.verifyComplete();
|
||||
|
||||
verifyZeroInteractions(connectionMock2);
|
||||
verifyZeroInteractions(connectionMock3);
|
||||
verify(connectionFactoryMock, times(1)).create();
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Hooks;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
@@ -65,8 +64,6 @@ public abstract class AbstractR2dbcRepositoryIntegrationTests extends R2dbcInteg
|
||||
@Before
|
||||
public void before() {
|
||||
|
||||
Hooks.onOperatorDebug();
|
||||
|
||||
this.jdbc = createJdbcTemplate(createDataSource());
|
||||
|
||||
try {
|
||||
|
||||
@@ -21,7 +21,6 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Hooks;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
@@ -69,8 +68,6 @@ public abstract class AbstractSimpleR2dbcRepositoryIntegrationTests extends R2db
|
||||
@Before
|
||||
public void before() {
|
||||
|
||||
Hooks.onOperatorDebug();
|
||||
|
||||
RelationalEntityInformation<LegoSet, Integer> entityInformation = new MappingRelationalEntityInformation<>(
|
||||
(RelationalPersistentEntity<LegoSet>) mappingContext.getRequiredPersistentEntity(LegoSet.class));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user