diff --git a/r2dbc/example/README.adoc b/r2dbc/example/README.adoc index 23fe2339..61f21e23 100644 --- a/r2dbc/example/README.adoc +++ b/r2dbc/example/README.adoc @@ -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. diff --git a/r2dbc/example/src/main/java/example/springdata/r2dbc/basics/Customer.java b/r2dbc/example/src/main/java/example/springdata/r2dbc/basics/Customer.java index 430e03b9..216a5f90 100644 --- a/r2dbc/example/src/main/java/example/springdata/r2dbc/basics/Customer.java +++ b/r2dbc/example/src/main/java/example/springdata/r2dbc/basics/Customer.java @@ -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; + } } diff --git a/r2dbc/example/src/main/java/example/springdata/r2dbc/basics/CustomerRepository.java b/r2dbc/example/src/main/java/example/springdata/r2dbc/basics/CustomerRepository.java index 1f1d30af..2c9ab58b 100644 --- a/r2dbc/example/src/main/java/example/springdata/r2dbc/basics/CustomerRepository.java +++ b/r2dbc/example/src/main/java/example/springdata/r2dbc/basics/CustomerRepository.java @@ -26,5 +26,5 @@ import org.springframework.data.repository.reactive.ReactiveCrudRepository; interface CustomerRepository extends ReactiveCrudRepository { @Query("select id, firstname, lastname from customer c where c.lastname = :lastname") - Flux findByLastnameLike(String lastname); + Flux findByLastname(String lastname); } diff --git a/r2dbc/example/src/main/java/example/springdata/r2dbc/basics/TransactionalService.java b/r2dbc/example/src/main/java/example/springdata/r2dbc/basics/TransactionalService.java new file mode 100644 index 00000000..1ef984f3 --- /dev/null +++ b/r2dbc/example/src/main/java/example/springdata/r2dbc/basics/TransactionalService.java @@ -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 save(Customer customer) { + + return repository.save(customer).map(it -> { + + if (it.firstname.equals("Dave")) { + throw new IllegalStateException(); + } else { + return it; + } + }); + } +} diff --git a/r2dbc/example/src/test/java/example/springdata/r2dbc/basics/CustomerRepositoryIntegrationTests.java b/r2dbc/example/src/test/java/example/springdata/r2dbc/basics/CustomerRepositoryIntegrationTests.java index 6cda04a4..8eb7afce 100644 --- a/r2dbc/example/src/test/java/example/springdata/r2dbc/basics/CustomerRepositoryIntegrationTests.java +++ b/r2dbc/example/src/test/java/example/springdata/r2dbc/basics/CustomerRepositoryIntegrationTests.java @@ -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(); diff --git a/r2dbc/example/src/test/java/example/springdata/r2dbc/basics/InfrastructureConfiguration.java b/r2dbc/example/src/test/java/example/springdata/r2dbc/basics/InfrastructureConfiguration.java index 7cc52fdf..02426691 100644 --- a/r2dbc/example/src/test/java/example/springdata/r2dbc/basics/InfrastructureConfiguration.java +++ b/r2dbc/example/src/test/java/example/springdata/r2dbc/basics/InfrastructureConfiguration.java @@ -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 { } diff --git a/r2dbc/example/src/test/java/example/springdata/r2dbc/basics/TransactionalServiceIntegrationTests.java b/r2dbc/example/src/test/java/example/springdata/r2dbc/basics/TransactionalServiceIntegrationTests.java new file mode 100644 index 00000000..2f42065f --- /dev/null +++ b/r2dbc/example/src/test/java/example/springdata/r2dbc/basics/TransactionalServiceIntegrationTests.java @@ -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 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(); + } +} diff --git a/r2dbc/example/src/test/resources/application.properties b/r2dbc/example/src/test/resources/application.properties new file mode 100644 index 00000000..d7881a3e --- /dev/null +++ b/r2dbc/example/src/test/resources/application.properties @@ -0,0 +1,2 @@ +logging.level.org.springframework.transaction=TRACE +logging.level.org.springframework.data.r2dbc.connectionfactory=DEBUG diff --git a/r2dbc/pom.xml b/r2dbc/pom.xml index 91b64a33..cf794d9f 100644 --- a/r2dbc/pom.xml +++ b/r2dbc/pom.xml @@ -21,7 +21,8 @@ - 1.4.197 + 1.4.199 + 5.2.0.BUILD-SNAPSHOT @@ -29,7 +30,7 @@ io.r2dbc r2dbc-bom - Arabba-M7 + Arabba-BUILD-SNAPSHOT pom import @@ -39,14 +40,9 @@ - org.springframework.data - spring-data-r2dbc - 1.0.0.BUILD-SNAPSHOT - - - - io.r2dbc - r2dbc-spi + org.springframework.boot.experimental + spring-boot-starter-data-r2dbc + 0.1.0.BUILD-SNAPSHOT @@ -61,8 +57,8 @@ - io.projectreactor - reactor-core + org.springframework + spring-jdbc @@ -71,11 +67,6 @@ test - - io.projectreactor.addons - reactor-extra - - @@ -85,8 +76,7 @@ spring-data-next - Moore-BUILD-SNAPSHOT - + Moore-BUILD-SNAPSHOT