INT-4566: Introduce R2DBC Inbound Channel Adapter

JIRA: https://jira.spring.io/browse/INT-4566

* Some code clean up
This commit is contained in:
rohanmukesh12
2020-06-30 23:11:39 -04:00
committed by Artem Bilan
parent 7a09358582
commit eb3c6a017f
6 changed files with 512 additions and 121 deletions

View File

@@ -0,0 +1,179 @@
/*
* Copyright 2020 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.integration.r2dbc.inbound;
import java.util.Map;
import org.reactivestreams.Publisher;
import org.springframework.data.r2dbc.core.DatabaseClient;
import org.springframework.data.r2dbc.core.FetchSpec;
import org.springframework.data.r2dbc.core.R2dbcEntityOperations;
import org.springframework.data.relational.core.query.Query;
import org.springframework.expression.Expression;
import org.springframework.expression.TypeLocator;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.expression.spel.support.StandardTypeLocator;
import org.springframework.integration.endpoint.AbstractMessageSource;
import org.springframework.integration.expression.ExpressionUtils;
import org.springframework.util.Assert;
import reactor.core.publisher.Mono;
/**
* An instance of {@link org.springframework.integration.core.MessageSource} which returns
* a {@link org.springframework.messaging.Message} with a payload which is the result of
* execution of a {@link Query}. When {@code expectSingleResult} is false (default), the R2dbc
* {@link Query} is executed using {@link R2dbcEntityOperations#select(Query, Class)} method which
* returns a {@link reactor.core.publisher.Flux}.
* The returned {@link reactor.core.publisher.Flux} will be used as the payload of the
* {@link org.springframework.messaging.Message} returned by the {@link #receive()}
* method.
* <p>
* When {@code expectSingleResult} is true, the {@link R2dbcEntityOperations#selectOne(Query, Class)} is
* used instead, and the message payload will be a {@link reactor.core.publisher.Mono}
* for the single object returned from the query.
*
* @author Rohan Mukesh
* @author Artem Bilan
*
* @since 5.4
*/
public class R2dbcMessageSource extends AbstractMessageSource<Publisher<?>> {
private final DatabaseClient databaseClient;
private final Expression queryExpression;
private Class<?> payloadType = Map.class;
private boolean expectSingleResult = false;
private StandardEvaluationContext evaluationContext;
private volatile boolean initialized = false;
/**
* Create an instance with the provided {@link DatabaseClient} and SpEL expression
* which should resolve to a Relational 'query' string.
* It assumes that the {@link DatabaseClient} is fully initialized and ready to be used.
* The 'query' will be evaluated on every call to the {@link #receive()} method.
* @param databaseClient The reactive database client for performing database calls.
* @param query The query String.
*/
public R2dbcMessageSource(DatabaseClient databaseClient, String query) {
this(databaseClient, new LiteralExpression(query));
}
/**
* Create an instance with the provided {@link DatabaseClient} and SpEL expression
* which should resolve to a Relational 'query' string.
* It assumes that the {@link DatabaseClient} is fully initialized and ready to be used.
* The 'queryExpression' will be evaluated on every call to the {@link #receive()} method.
* @param databaseClient The reactive for performing database calls.
* @param queryExpression The query expression.
*/
public R2dbcMessageSource(DatabaseClient databaseClient, Expression queryExpression) {
Assert.notNull(databaseClient, "'databaseClient' must not be null");
Assert.notNull(queryExpression, "'queryExpression' must not be null");
this.databaseClient = databaseClient;
this.queryExpression = queryExpression;
}
/**
* Provide a way to set the type of the entityClass that will be passed to the
* {@link org.springframework.data.r2dbc.core.DatabaseClient#execute(String)}
* method.
* @param payloadType The t class.
*/
public void setPayloadType(Class<?> payloadType) {
Assert.notNull(payloadType, "'payloadType' must not be null");
this.payloadType = payloadType;
}
/**
* Provide a way to manage which find* method to invoke on {@link R2dbcEntityOperations}.
* Default is 'false', which means the {@link #receive()} method will use
* the {@link org.springframework.data.r2dbc.core.DatabaseClient#execute(String)} method and will fetch all. If set
* to 'true'{@link #receive()} will use {@link org.springframework.data.r2dbc.core.DatabaseClient#execute(String)}
* and will fetch one and the payload of the returned {@link org.springframework.messaging.Message}
* will be the returned target Object of type
* identified by {@link #payloadType} instead of a List.
* @param expectSingleResult true if a single result is expected.
*/
public void setExpectSingleResult(boolean expectSingleResult) {
this.expectSingleResult = expectSingleResult;
}
@Override
public String getComponentType() {
return "r2dbc:inbound-channel-adapter";
}
@Override
protected void onInit() {
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory());
TypeLocator typeLocator = this.evaluationContext.getTypeLocator();
if (typeLocator instanceof StandardTypeLocator) {
/*
* Register the R2dbc Query DSL package so they don't need a FQCN for QueryBuilder, for example.
*/
((StandardTypeLocator) typeLocator).registerImport("org.springframework.data.relational.core.query");
}
this.initialized = true;
}
/**
* Execute a {@link Query} returning its results as the Message payload.
* The payload can be either {@link reactor.core.publisher.Flux} or
* {@link reactor.core.publisher.Mono} of objects of type identified by {@link #payloadType},
* or a single element of type identified by {@link #payloadType}
* based on the value of {@link #expectSingleResult} attribute which defaults to 'false' resulting
* {@link org.springframework.messaging.Message} with payload of type
* {@link reactor.core.publisher.Flux}. The collection name used in the
*/
@Override
protected Object doReceive() {
Assert.isTrue(this.initialized, "This class is not yet initialized. Invoke its afterPropertiesSet() method");
Mono<FetchSpec<?>> queryMono =
Mono.fromSupplier(() -> this.queryExpression.getValue(this.evaluationContext))
.map(this::prepareFetch);
if (this.expectSingleResult) {
return queryMono.flatMap(FetchSpec::one);
}
return queryMono.flatMapMany(FetchSpec::all);
}
private FetchSpec<?> prepareFetch(Object queryObject) {
String queryString = evaluateQueryObject(queryObject);
return this.databaseClient
.execute(queryString)
.as(this.payloadType)
.fetch();
}
private String evaluateQueryObject(Object queryObject) {
if (queryObject instanceof String) {
return (String) queryObject;
}
throw new IllegalStateException("'queryExpression' must evaluate to String " +
"or org.springframework.data.relational.core.query.Query, but not: " + queryObject);
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2020 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.integration.r2dbc.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration;
import org.springframework.data.r2dbc.core.DatabaseClient;
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
import io.r2dbc.h2.H2ConnectionConfiguration;
import io.r2dbc.h2.H2ConnectionFactory;
import io.r2dbc.spi.ConnectionFactory;
/**
* @author Rohan Mukesh
*
* @since 5.4
*/
@Configuration
@EnableR2dbcRepositories(basePackages = "org.springframework.integration.r2dbc.repository")
public class R2dbcDatabaseConfiguration extends AbstractR2dbcConfiguration {
@Bean
@Override
public ConnectionFactory connectionFactory() {
return createConnectionFactory();
}
public static ConnectionFactory createConnectionFactory() {
return new H2ConnectionFactory(H2ConnectionConfiguration.builder()
.inMemory("r2dbc")
.username("sa")
.password("")
.option("DB_CLOSE_DELAY=-1").build());
}
@Bean
public DatabaseClient databaseClient(ConnectionFactory connectionFactory) {
return DatabaseClient.create(connectionFactory);
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.integration.r2dbc.outbound;
package org.springframework.integration.r2dbc.entity;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;
@@ -25,14 +25,14 @@ import org.springframework.data.relational.core.mapping.Table;
* @since 5.4
*/
@Table
class Person {
public class Person {
@Id
Integer id;
private Integer id;
String name;
private String name;
Integer age;
private Integer age;
public void setId(Integer id) {
this.id = id;
@@ -46,7 +46,7 @@ class Person {
this.age = age;
}
Person(String name, Integer age) {
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}

View File

@@ -0,0 +1,173 @@
/*
* Copyright 2020 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.integration.r2dbc.inbound;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.r2dbc.core.DatabaseClient;
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
import org.springframework.integration.expression.ValueExpression;
import org.springframework.integration.r2dbc.config.R2dbcDatabaseConfiguration;
import org.springframework.integration.r2dbc.entity.Person;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Hooks;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
/**
* @author Rohan Mukesh
* @author Artem Bilan
*
* @since 5.4
*/
@SpringJUnitConfig
@DirtiesContext
public class R2dbcMessageSourceTests {
@Autowired
DatabaseClient client;
R2dbcEntityTemplate entityTemplate;
@Autowired
R2dbcMessageSource r2dbcMessageSourceSelectOne;
@Autowired
R2dbcMessageSource r2dbcMessageSourceSelectMany;
@Autowired
R2dbcMessageSource r2dbcMessageSourceError;
@BeforeEach
public void setup() {
Hooks.onOperatorDebug();
entityTemplate = new R2dbcEntityTemplate(this.client);
List<String> statements = Arrays.asList(
"DROP TABLE IF EXISTS person;",
"CREATE table person (id INT AUTO_INCREMENT NOT NULL, name VARCHAR2, age INT NOT NULL);");
statements.forEach(it -> this.client.execute(it)
.fetch()
.rowsUpdated()
.as(StepVerifier::create)
.expectNextCount(1)
.verifyComplete());
}
@Test
public void validateSuccessfulQueryWithoutSettingExpectedElement() {
this.entityTemplate.insert(new Person("Bob", 35))
.then()
.as(StepVerifier::create)
.verifyComplete();
StepVerifier.create((Flux<?>) r2dbcMessageSourceSelectMany.receive().getPayload())
.assertNext(person -> assertThat(((Person) person).getName()).isEqualTo("Bob"))
.verifyComplete();
}
@Test
public void validateSuccessfulQueryWithSingleElementOfMonoDBObject() {
this.entityTemplate.insert(new Person("Bob", 35))
.then()
.as(StepVerifier::create)
.verifyComplete();
r2dbcMessageSourceSelectOne.setExpectSingleResult(true);
StepVerifier.create((Mono<?>) r2dbcMessageSourceSelectOne.receive().getPayload())
.assertNext(person -> assertThat(((Person) person).getName()).isEqualTo("Bob"))
.verifyComplete();
}
@Test
public void validateSuccessfulQueryWithMultipleElementOfFluxDBObject() {
this.entityTemplate.insert(new Person("Bob", 35))
.then()
.as(StepVerifier::create)
.verifyComplete();
this.entityTemplate.insert(new Person("Tom", 40))
.then()
.as(StepVerifier::create)
.verifyComplete();
StepVerifier.create((Flux<?>) r2dbcMessageSourceSelectMany.receive().getPayload())
.assertNext(person -> assertThat(((Person) person).getName()).isEqualTo("Bob"))
.assertNext(person -> assertThat(((Person) person).getName()).isEqualTo("Tom"))
.verifyComplete();
}
@Test
public void testAnyOtherObjectQueryExpression() {
StepVerifier.create((Flux<?>) r2dbcMessageSourceError.receive().getPayload())
.expectErrorMatches(throwable -> throwable instanceof IllegalStateException
&& throwable.getMessage().contains("'queryExpression' must evaluate to String or"))
.verify();
}
@Configuration
@Import(R2dbcDatabaseConfiguration.class)
static class R2dbcMessageSourceConfiguration {
@Autowired
DatabaseClient databaseClient;
@Bean
public R2dbcMessageSource r2dbcMessageSourceSelectOne() {
R2dbcMessageSource r2dbcMessageSource = new R2dbcMessageSource(databaseClient,
"select * from person Where id = 1");
r2dbcMessageSource.setPayloadType(Person.class);
return r2dbcMessageSource;
}
@Bean
public R2dbcMessageSource r2dbcMessageSourceSelectMany() {
R2dbcMessageSource r2dbcMessageSource = new R2dbcMessageSource(databaseClient, "select * from person");
r2dbcMessageSource.setPayloadType(Person.class);
return r2dbcMessageSource;
}
@Bean
public R2dbcMessageSource r2dbcMessageSourceError() {
R2dbcMessageSource r2dbcMessageSource = new R2dbcMessageSource(databaseClient,
new ValueExpression<>(new Object()));
r2dbcMessageSource.setPayloadType(Person.class);
return r2dbcMessageSource;
}
}
}

View File

@@ -17,8 +17,6 @@
package org.springframework.integration.r2dbc.outbound;
import static org.mockito.Mockito.mock;
import java.time.Duration;
import java.util.Arrays;
import java.util.HashMap;
@@ -29,79 +27,51 @@ import java.util.Optional;
import org.junit.Assert;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Answers;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration;
import org.springframework.context.annotation.Import;
import org.springframework.data.r2dbc.core.DatabaseClient;
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
import org.springframework.data.relational.core.query.Criteria;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import org.springframework.integration.expression.FunctionExpression;
import org.springframework.integration.r2dbc.config.R2dbcDatabaseConfiguration;
import org.springframework.integration.r2dbc.entity.Person;
import org.springframework.integration.r2dbc.repository.PersonRepository;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import io.r2dbc.h2.H2ConnectionConfiguration;
import io.r2dbc.h2.H2ConnectionFactory;
import io.r2dbc.spi.ConnectionFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Hooks;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
/**
* @author Rohan Mukesh
* @author Rohan Mukesh
*
* @since 5.4
* @since 5.4
*/
@SpringJUnitConfig
public class R2DbcMessageHandlerTests {
@DirtiesContext
public class R2dbcMessageHandlerTests {
@Autowired
DatabaseClient client;
@Autowired
H2ConnectionFactory factory;
R2dbcEntityTemplate entityTemplate;
@Autowired
PersonRepository personRepository;
@Configuration
@EnableR2dbcRepositories(considerNestedRepositories = true,
includeFilters = @ComponentScan.Filter(classes = PersonRepository.class, type = FilterType.ASSIGNABLE_TYPE))
static class IntegrationTestConfiguration extends AbstractR2dbcConfiguration {
@Bean
@Override
public ConnectionFactory connectionFactory() {
return createConnectionFactory();
}
}
public static ConnectionFactory createConnectionFactory() {
return new H2ConnectionFactory(H2ConnectionConfiguration.builder()
.inMemory("r2dbc")
.username("sa")
.password("")
.option("DB_CLOSE_DELAY=-1").build());
}
@Autowired
R2dbcMessageHandler r2dbcMessageHandler;
@BeforeEach
public void setup() {
entityTemplate = new R2dbcEntityTemplate(client);
Hooks.onOperatorDebug();
r2dbcMessageHandler.setQueryType(R2dbcMessageHandler.Type.INSERT);
r2dbcMessageHandler.setTableNameExpression(null);
List<String> statements = Arrays.asList(
"DROP TABLE IF EXISTS person;",
"CREATE table person (id INT AUTO_INCREMENT NOT NULL, name VARCHAR2, age INT NOT NULL);");
@@ -112,17 +82,12 @@ public class R2DbcMessageHandlerTests {
.as(StepVerifier::create)
.expectNextCount(1)
.verifyComplete());
}
@Test
public void validateMessageHandlingWithDefaultInsertCollection() {
R2dbcMessageHandler handler = new R2dbcMessageHandler(this.entityTemplate);
handler.setBeanFactory(mock(BeanFactory.class));
handler.setApplicationContext(mock(ApplicationContext.class, Answers.RETURNS_MOCKS));
handler.afterPropertiesSet();
Message<Person> message = MessageBuilder.withPayload(this.createPerson("Bob", 35)).build();
waitFor(handler.handleMessage(message));
Message<Person> message = MessageBuilder.withPayload(createPerson("Bob", 35)).build();
waitFor(r2dbcMessageHandler.handleMessage(message));
personRepository.findAll()
.as(StepVerifier::create)
@@ -132,18 +97,14 @@ public class R2DbcMessageHandlerTests {
@Test
public void validateMessageHandlingWithInsertQueryCollection() {
R2dbcMessageHandler handler = new R2dbcMessageHandler(this.entityTemplate);
handler.setBeanFactory(mock(BeanFactory.class));
handler.setApplicationContext(mock(ApplicationContext.class, Answers.RETURNS_MOCKS));
handler.setValuesExpression(new FunctionExpression<Message<?>>(Message::getPayload));
handler.setQueryType(R2dbcMessageHandler.Type.INSERT);
handler.setTableName("person");
handler.afterPropertiesSet();
r2dbcMessageHandler.setValuesExpression(new FunctionExpression<Message<?>>(Message::getPayload));
r2dbcMessageHandler.setQueryType(R2dbcMessageHandler.Type.INSERT);
r2dbcMessageHandler.setTableName("person");
Map<String, Object> payload = new HashMap<>();
payload.put("name", "rohan");
payload.put("age", 35);
Message<?> message = MessageBuilder.withPayload(payload).build();
waitFor(handler.handleMessage(message));
waitFor(r2dbcMessageHandler.handleMessage(message));
Flux<?> all = client.execute("SELECT name, age FROM person")
.fetch().all();
@@ -155,14 +116,10 @@ public class R2DbcMessageHandlerTests {
@Test
public void validateMessageHandlingWithDefaultUpdateCollection() {
R2dbcMessageHandler handler = new R2dbcMessageHandler(this.entityTemplate);
handler.setBeanFactory(mock(BeanFactory.class));
handler.setApplicationContext(mock(ApplicationContext.class, Answers.RETURNS_MOCKS));
handler.afterPropertiesSet();
Message<Person> message = MessageBuilder.withPayload(this.createPerson("Bob", 35)).build();
waitFor(handler.handleMessage(message));
Message<Person> message = MessageBuilder.withPayload(createPerson("Bob", 35)).build();
waitFor(r2dbcMessageHandler.handleMessage(message));
handler.setQueryType(R2dbcMessageHandler.Type.UPDATE);
r2dbcMessageHandler.setQueryType(R2dbcMessageHandler.Type.UPDATE);
Person person = this.client.select()
.from("person")
@@ -175,39 +132,33 @@ public class R2DbcMessageHandlerTests {
message = MessageBuilder.withPayload(person)
.build();
waitFor(handler.handleMessage(message));
waitFor(r2dbcMessageHandler.handleMessage(message));
personRepository.findAll()
.as(StepVerifier::create)
.consumeNextWith(p -> Assert.assertEquals(Optional.of(40), Optional.ofNullable(p.age)))
.consumeNextWith(p -> Assert.assertEquals(Optional.of(40), Optional.ofNullable(p.getAge())))
.verifyComplete();
}
@Test
public void validateMessageHandlingWithUpdateQueryCollection() {
R2dbcMessageHandler handler = new R2dbcMessageHandler(this.entityTemplate);
handler.setBeanFactory(mock(BeanFactory.class));
handler.setApplicationContext(mock(ApplicationContext.class, Answers.RETURNS_MOCKS));
handler.setValuesExpression(new FunctionExpression<Message<?>>(Message::getPayload));
handler.setQueryType(R2dbcMessageHandler.Type.INSERT);
handler.setTableName("person");
handler.afterPropertiesSet();
r2dbcMessageHandler.setValuesExpression(new FunctionExpression<Message<?>>(Message::getPayload));
r2dbcMessageHandler.setQueryType(R2dbcMessageHandler.Type.INSERT);
r2dbcMessageHandler.setTableName("person");
Map<String, Object> payload = new HashMap<>();
payload.put("name", "Bob");
payload.put("age", 35);
Message<?> message = MessageBuilder.withPayload(payload).build();
waitFor(handler.handleMessage(message));
waitFor(r2dbcMessageHandler.handleMessage(message));
payload = new HashMap<>();
payload.put("name", "Rob");
payload.put("age", 43);
message = MessageBuilder.withPayload(payload).build();
waitFor(handler.handleMessage(message));
waitFor(r2dbcMessageHandler.handleMessage(message));
payload = new HashMap<>();
handler.setQueryType(R2dbcMessageHandler.Type.UPDATE);
r2dbcMessageHandler.setQueryType(R2dbcMessageHandler.Type.UPDATE);
Object insertedId = client.execute("SELECT id FROM person")
.fetch()
@@ -215,11 +166,12 @@ public class R2DbcMessageHandlerTests {
.block()
.get("id");
handler.setCriteriaExpression(new FunctionExpression<Message<?>>((m) -> Criteria.where("id").is(insertedId)));
r2dbcMessageHandler.setCriteriaExpression(
new FunctionExpression<Message<?>>((m) -> Criteria.where("id").is(insertedId)));
payload.put("age", 40);
message = MessageBuilder.withPayload(payload).build();
waitFor(handler.handleMessage(message));
waitFor(r2dbcMessageHandler.handleMessage(message));
Flux<?> all = client.execute("SELECT age,name FROM person where age=40")
.fetch().all();
@@ -231,12 +183,8 @@ public class R2DbcMessageHandlerTests {
@Test
public void validateMessageHandlingWithDefaultDeleteCollection() {
R2dbcMessageHandler handler = new R2dbcMessageHandler(this.entityTemplate);
handler.setBeanFactory(mock(BeanFactory.class));
handler.setApplicationContext(mock(ApplicationContext.class, Answers.RETURNS_MOCKS));
handler.afterPropertiesSet();
Message<Person> message = MessageBuilder.withPayload(this.createPerson("Bob", 35)).build();
waitFor(handler.handleMessage(message));
Message<Person> message = MessageBuilder.withPayload(createPerson("Bob", 35)).build();
waitFor(r2dbcMessageHandler.handleMessage(message));
Person person = this.client
.select()
@@ -246,9 +194,9 @@ public class R2DbcMessageHandlerTests {
.first()
.block();
handler.setQueryType(R2dbcMessageHandler.Type.DELETE);
r2dbcMessageHandler.setQueryType(R2dbcMessageHandler.Type.DELETE);
message = MessageBuilder.withPayload(person).build();
waitFor(handler.handleMessage(message));
waitFor(r2dbcMessageHandler.handleMessage(message));
personRepository.findAll()
.as(StepVerifier::create)
@@ -258,22 +206,17 @@ public class R2DbcMessageHandlerTests {
@Test
public void validateMessageHandlingWithDeleteQueryCollection() {
R2dbcMessageHandler handler = new R2dbcMessageHandler(this.entityTemplate);
handler.setBeanFactory(mock(BeanFactory.class));
handler.setApplicationContext(mock(ApplicationContext.class, Answers.RETURNS_MOCKS));
handler.setValuesExpression(new FunctionExpression<Message<?>>(Message::getPayload));
handler.setQueryType(R2dbcMessageHandler.Type.INSERT);
handler.setTableName("person");
handler.afterPropertiesSet();
r2dbcMessageHandler.setValuesExpression(new FunctionExpression<Message<?>>(Message::getPayload));
r2dbcMessageHandler.setQueryType(R2dbcMessageHandler.Type.INSERT);
r2dbcMessageHandler.setTableName("person");
Map<String, Object> payload = new HashMap<>();
payload.put("name", "Bob");
payload.put("age", 35);
Message<?> message = MessageBuilder.withPayload(payload).build();
waitFor(handler.handleMessage(message));
waitFor(r2dbcMessageHandler.handleMessage(message));
payload = new HashMap<>();
handler.setQueryType(R2dbcMessageHandler.Type.DELETE);
r2dbcMessageHandler.setQueryType(R2dbcMessageHandler.Type.DELETE);
Object insertedId = client.execute("SELECT id FROM person")
.fetch()
@@ -281,9 +224,10 @@ public class R2DbcMessageHandlerTests {
.block()
.get("id");
handler.setCriteriaExpression(new FunctionExpression<Message<?>>((m) -> Criteria.where("id").is(insertedId)));
r2dbcMessageHandler.setCriteriaExpression(
new FunctionExpression<Message<?>>((m) -> Criteria.where("id").is(insertedId)));
message = MessageBuilder.withPayload(payload).build();
waitFor(handler.handleMessage(message));
waitFor(r2dbcMessageHandler.handleMessage(message));
Flux<?> all = client.execute("SELECT age,name FROM person where age=35")
.fetch().all();
@@ -295,28 +239,23 @@ public class R2DbcMessageHandlerTests {
@Test
public void validateMessageHandlingWithDeleteQueryCollection_MultipleRows() {
R2dbcMessageHandler handler = new R2dbcMessageHandler(this.entityTemplate);
handler.setBeanFactory(mock(BeanFactory.class));
handler.setApplicationContext(mock(ApplicationContext.class, Answers.RETURNS_MOCKS));
handler.setValuesExpression(new FunctionExpression<Message<?>>(Message::getPayload));
handler.setQueryType(R2dbcMessageHandler.Type.INSERT);
handler.setTableName("person");
handler.afterPropertiesSet();
r2dbcMessageHandler.setValuesExpression(new FunctionExpression<Message<?>>(Message::getPayload));
r2dbcMessageHandler.setQueryType(R2dbcMessageHandler.Type.INSERT);
r2dbcMessageHandler.setTableName("person");
Map<String, Object> payload = new HashMap<>();
payload.put("name", "Bob");
payload.put("age", 35);
Message<?> message = MessageBuilder.withPayload(payload).build();
waitFor(handler.handleMessage(message));
waitFor(r2dbcMessageHandler.handleMessage(message));
payload = new HashMap<>();
payload.put("name", "Rob");
payload.put("age", 40);
message = MessageBuilder.withPayload(payload).build();
waitFor(handler.handleMessage(message));
waitFor(r2dbcMessageHandler.handleMessage(message));
payload = new HashMap<>();
handler.setQueryType(R2dbcMessageHandler.Type.DELETE);
r2dbcMessageHandler.setQueryType(R2dbcMessageHandler.Type.DELETE);
Object insertedId = client.execute("SELECT id FROM person")
.fetch()
@@ -324,9 +263,10 @@ public class R2DbcMessageHandlerTests {
.block()
.get("id");
handler.setCriteriaExpression(new FunctionExpression<Message<?>>((m) -> Criteria.where("id").is(insertedId)));
r2dbcMessageHandler.setCriteriaExpression(
new FunctionExpression<Message<?>>((m) -> Criteria.where("id").is(insertedId)));
message = MessageBuilder.withPayload(payload).build();
waitFor(handler.handleMessage(message));
waitFor(r2dbcMessageHandler.handleMessage(message));
Flux<?> all = client.execute("SELECT age,name FROM person where age=40")
.fetch().all();
@@ -345,7 +285,18 @@ public class R2DbcMessageHandlerTests {
return mono.block(Duration.ofSeconds(10));
}
interface PersonRepository extends ReactiveCrudRepository<Person, Integer> {
@Configuration
@Import(R2dbcDatabaseConfiguration.class)
static class R2dbcMessageHandlerConfiguration {
@Autowired
DatabaseClient databaseClient;
@Bean
public R2dbcMessageHandler r2dbcMessageHandler() {
return new R2dbcMessageHandler(new R2dbcEntityTemplate(databaseClient));
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2020 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.integration.r2dbc.repository;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import org.springframework.integration.r2dbc.entity.Person;
/**
* @author Rohan Mukesh
*
* @since 5.4
*/
public interface PersonRepository extends ReactiveCrudRepository<Person, Integer> {
}