#500 - Example for declarative reactive transactions in R2DBC.
Introduced transactional service using @Transactional on the method declaration. Using the experimental Spring Boot integration to get both an H2ConnectionFactory and R2dbcTransactionManager configured. Added test case to show the rollbacks kicking in on an exception in a map operator.
This commit is contained in:
@@ -7,3 +7,4 @@ This projects shows some sample usage of the work-in-progress R2DBC support for
|
||||
- `InfrastructureConfiguration` - sets up a R2DBC `ConnectionFactory` based on the R2DBC Postgres driver (https://github.com/r2dbc/r2dbc-postgresql[r2dbc-postgresql]), a `DatabaseClient` and a `R2dbcRepositoryFactory` to eventually create a `CustomerRepository`.
|
||||
- `CustomerRepository` - a standard Spring Data reactive CRUD repository exposing query methods using manually defined queries
|
||||
- `CustomerRepositoryIntegrationTests` - to initialize the database with some setup SQL and the inserting and reading `Customer` instances.
|
||||
- `TransactionalService` - uses declarative transaction to apply a transactional boundary to repository operations.
|
||||
|
||||
@@ -17,7 +17,6 @@ package example.springdata.r2dbc.basics;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.Value;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
|
||||
@@ -27,6 +26,11 @@ import org.springframework.data.annotation.Id;
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
class Customer {
|
||||
|
||||
@Id Integer id;
|
||||
String firstname, lastname;
|
||||
|
||||
boolean hasId() {
|
||||
return id != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,5 +26,5 @@ import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||
interface CustomerRepository extends ReactiveCrudRepository<Customer, Long> {
|
||||
|
||||
@Query("select id, firstname, lastname from customer c where c.lastname = :lastname")
|
||||
Flux<Customer> findByLastnameLike(String lastname);
|
||||
Flux<Customer> findByLastname(String lastname);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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 example.springdata.r2dbc.basics;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
class TransactionalService {
|
||||
|
||||
private final @NonNull CustomerRepository repository;
|
||||
|
||||
/**
|
||||
* Saves the given {@link Customer} unless its firstname is "Dave".
|
||||
*
|
||||
* @param customer must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@Transactional
|
||||
public Mono<Customer> save(Customer customer) {
|
||||
|
||||
return repository.save(customer).map(it -> {
|
||||
|
||||
if (it.firstname.equals("Dave")) {
|
||||
throw new IllegalStateException();
|
||||
} else {
|
||||
return it;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.data.r2dbc.function.DatabaseClient;
|
||||
import org.springframework.data.r2dbc.core.DatabaseClient;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
/**
|
||||
@@ -81,7 +81,7 @@ public class CustomerRepositoryIntegrationTests {
|
||||
|
||||
insertCustomers(dave, carter);
|
||||
|
||||
customers.findByLastnameLike("Matthews") //
|
||||
customers.findByLastname("Matthews") //
|
||||
.as(StepVerifier::create) //
|
||||
.assertNext(dave::equals) //
|
||||
.verifyComplete();
|
||||
|
||||
@@ -15,31 +15,11 @@
|
||||
*/
|
||||
package example.springdata.r2dbc.basics;
|
||||
|
||||
import io.r2dbc.h2.H2ConnectionConfiguration;
|
||||
import io.r2dbc.h2.H2ConnectionFactory;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration;
|
||||
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Configuration
|
||||
@EnableR2dbcRepositories
|
||||
class InfrastructureConfiguration extends AbstractR2dbcConfiguration {
|
||||
|
||||
@Bean
|
||||
@Override
|
||||
public H2ConnectionFactory connectionFactory() {
|
||||
|
||||
H2ConnectionConfiguration config = H2ConnectionConfiguration.builder() //
|
||||
.inMemory("test-database2") //
|
||||
.option("DB_CLOSE_DELAY=-1") //
|
||||
.build();
|
||||
|
||||
return new H2ConnectionFactory(config);
|
||||
}
|
||||
}
|
||||
@SpringBootApplication
|
||||
class InfrastructureConfiguration { }
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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 example.springdata.r2dbc.basics;
|
||||
|
||||
import reactor.core.publisher.Hooks;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.data.r2dbc.core.DatabaseClient;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link TransactionalService}.
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
* @soundtrack Shame - Tedeschi Trucks Band (Signs)
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = InfrastructureConfiguration.class)
|
||||
public class TransactionalServiceIntegrationTests {
|
||||
|
||||
@Autowired TransactionalService service;
|
||||
@Autowired CustomerRepository repository;
|
||||
@Autowired DatabaseClient database;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
Hooks.onOperatorDebug();
|
||||
|
||||
List<String> statements = Arrays.asList(//
|
||||
"DROP TABLE IF EXISTS customer;",
|
||||
"CREATE TABLE customer ( id SERIAL PRIMARY KEY, firstname VARCHAR(100) NOT NULL, lastname VARCHAR(100) NOT NULL);");
|
||||
|
||||
statements.forEach(it -> database.execute() //
|
||||
.sql(it) //
|
||||
.fetch() //
|
||||
.rowsUpdated() //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(1) //
|
||||
.verifyComplete());
|
||||
}
|
||||
|
||||
@Test // #500
|
||||
public void exceptionTriggersRollback() {
|
||||
|
||||
service.save(new Customer(null, "Dave", "Matthews")) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectError() // Error because of the exception triggered within the service
|
||||
.verify();
|
||||
|
||||
// No data inserted due to rollback
|
||||
repository.findByLastname("Matthews") //
|
||||
.as(StepVerifier::create) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // #500
|
||||
public void insertsDataTransactionally() {
|
||||
|
||||
service.save(new Customer(null, "Carter", "Beauford")) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextMatches(Customer::hasId) //
|
||||
.verifyComplete();
|
||||
|
||||
// Data inserted due to commit
|
||||
repository.findByLastname("Beauford") //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextMatches(Customer::hasId) //
|
||||
.verifyComplete();
|
||||
}
|
||||
}
|
||||
2
r2dbc/example/src/test/resources/application.properties
Normal file
2
r2dbc/example/src/test/resources/application.properties
Normal file
@@ -0,0 +1,2 @@
|
||||
logging.level.org.springframework.transaction=TRACE
|
||||
logging.level.org.springframework.data.r2dbc.connectionfactory=DEBUG
|
||||
@@ -21,7 +21,8 @@
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<h2.version>1.4.197</h2.version>
|
||||
<h2.version>1.4.199</h2.version>
|
||||
<spring.version>5.2.0.BUILD-SNAPSHOT</spring.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
@@ -29,7 +30,7 @@
|
||||
<dependency>
|
||||
<groupId>io.r2dbc</groupId>
|
||||
<artifactId>r2dbc-bom</artifactId>
|
||||
<version>Arabba-M7</version>
|
||||
<version>Arabba-BUILD-SNAPSHOT</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
@@ -39,14 +40,9 @@
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-r2dbc</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.r2dbc</groupId>
|
||||
<artifactId>r2dbc-spi</artifactId>
|
||||
<groupId>org.springframework.boot.experimental</groupId>
|
||||
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
|
||||
<version>0.1.0.BUILD-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -61,8 +57,8 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-jdbc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -71,11 +67,6 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.projectreactor.addons</groupId>
|
||||
<artifactId>reactor-extra</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
@@ -85,8 +76,7 @@
|
||||
<profile>
|
||||
<id>spring-data-next</id>
|
||||
<properties>
|
||||
<spring-data-releasetrain.version>Moore-BUILD-SNAPSHOT
|
||||
</spring-data-releasetrain.version>
|
||||
<spring-data-releasetrain.version>Moore-BUILD-SNAPSHOT</spring-data-releasetrain.version>
|
||||
</properties>
|
||||
</profile>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user