INT-4566: Introduce R2DBC Inbound Channel Adapter
JIRA: https://jira.spring.io/browse/INT-4566 * Some code clean up
This commit is contained in:
committed by
Artem Bilan
parent
7a09358582
commit
eb3c6a017f
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user