From 1bba73fc060faec6141e2edd957c98ad6a56615c Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Mon, 14 Nov 2016 13:15:33 -0500 Subject: [PATCH] INT-4158: Port Java DSL for JPA Module JIRA: https://jira.spring.io/browse/INT-4158 * Add JavaDocs * De-Boot `JpaDslTests` * Fix typos and generics inconsistency in the `JpaOutboundGatewayFactoryBean` Address PR comments * Get rid of `JpaOutboundGatewayFactoryBean` usage in the `JpaBaseOutboundEndpointSpec` * Rework `JpaBaseOutboundEndpointSpec` and its inheritors logic to use `JpaOutboundGateway` directly * Rename to the `JpaTests` * Fix JavaDoc in the `IntegrationFlowDefinition` * Do not use `jpaParameters` in the `JpaExecutor` if it is empty collection, not only null --- .../dsl/IntegrationFlowDefinition.java | 1 + .../integration/jpa/core/JpaExecutor.java | 7 +- .../integration/jpa/dsl/Jpa.java | 176 ++++++++++++ .../jpa/dsl/JpaBaseOutboundEndpointSpec.java | 167 +++++++++++ .../jpa/dsl/JpaInboundChannelAdapterSpec.java | 190 +++++++++++++ .../dsl/JpaRetrievingOutboundGatewaySpec.java | 177 ++++++++++++ .../dsl/JpaUpdatingOutboundEndpointSpec.java | 95 +++++++ .../integration/jpa/dsl/package-info.java | 4 + .../JpaOutboundGatewayFactoryBean.java | 4 +- .../integration/jpa/dsl/JpaTests.java | 261 ++++++++++++++++++ 10 files changed, 1076 insertions(+), 6 deletions(-) create mode 100644 spring-integration-jpa/src/main/java/org/springframework/integration/jpa/dsl/Jpa.java create mode 100644 spring-integration-jpa/src/main/java/org/springframework/integration/jpa/dsl/JpaBaseOutboundEndpointSpec.java create mode 100644 spring-integration-jpa/src/main/java/org/springframework/integration/jpa/dsl/JpaInboundChannelAdapterSpec.java create mode 100644 spring-integration-jpa/src/main/java/org/springframework/integration/jpa/dsl/JpaRetrievingOutboundGatewaySpec.java create mode 100644 spring-integration-jpa/src/main/java/org/springframework/integration/jpa/dsl/JpaUpdatingOutboundEndpointSpec.java create mode 100644 spring-integration-jpa/src/main/java/org/springframework/integration/jpa/dsl/package-info.java create mode 100644 spring-integration-jpa/src/test/java/org/springframework/integration/jpa/dsl/JpaTests.java diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlowDefinition.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlowDefinition.java index 6e9e331656..a58eeaabe2 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlowDefinition.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlowDefinition.java @@ -2761,6 +2761,7 @@ public abstract class IntegrationFlowDefinition the expected {@code payload} type * @return the Reactive Streams {@link Publisher} */ @SuppressWarnings("unchecked") diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/JpaExecutor.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/JpaExecutor.java index e51e7e36b8..78fc20afbc 100644 --- a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/JpaExecutor.java +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/JpaExecutor.java @@ -39,6 +39,7 @@ import org.springframework.integration.jpa.support.parametersource.ParameterSour import org.springframework.messaging.Message; import org.springframework.messaging.MessagingException; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; /** * Executes Jpa Operations that produce payload objects from the result of the provided: @@ -172,7 +173,7 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware { @Override public void afterPropertiesSet() { - if (this.jpaParameters != null) { + if (!CollectionUtils.isEmpty(this.jpaParameters)) { if (this.parameterSourceFactory == null) { ExpressionEvaluatingParameterSourceFactory expressionSourceFactory = @@ -597,18 +598,16 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware { this.firstResultExpression = firstResultExpression; } - /** * Set the expression that will be evaluated to get the {@code primaryKey} for * {@link javax.persistence.EntityManager#find(Class, Object)} - * @param idExpression The first result expression. + * @param idExpression the SpEL expression for entity {@code primaryKey}. * @since 4.0 */ public void setIdExpression(Expression idExpression) { this.idExpression = idExpression; } - /** * Set the expression for maximum number of results expression. It has be a non null value * Not setting one will default to the behavior of fetching all the records diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/dsl/Jpa.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/dsl/Jpa.java new file mode 100644 index 0000000000..a32d1240d2 --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/dsl/Jpa.java @@ -0,0 +1,176 @@ +/* + * Copyright 2016 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.integration.jpa.dsl; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; + +import org.springframework.integration.jpa.core.JpaExecutor; +import org.springframework.integration.jpa.core.JpaOperations; + +/** + * Factory class for JPA components. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public final class Jpa { + + /** + * Create a {@link JpaInboundChannelAdapterSpec} builder instance + * based on the provided {@link EntityManagerFactory}. + * @param entityManagerFactory the {@link EntityManagerFactory} to use + * @return the JpaInboundChannelAdapterSpec instance + */ + public static JpaInboundChannelAdapterSpec inboundAdapter(EntityManagerFactory entityManagerFactory) { + return inboundAdapter(new JpaExecutor(entityManagerFactory)); + } + + /** + * Create a {@link JpaInboundChannelAdapterSpec} builder instance + * based on the provided {@link EntityManager}. + * @param entityManager the {@link EntityManager} to use + * @return the JpaInboundChannelAdapterSpec instance + */ + public static JpaInboundChannelAdapterSpec inboundAdapter(EntityManager entityManager) { + return inboundAdapter(new JpaExecutor(entityManager)); + } + + /** + * Create a {@link JpaInboundChannelAdapterSpec} builder instance + * based on the provided {@link JpaOperations}. + * @param jpaOperations the {@link JpaOperations} to use + * @return the JpaInboundChannelAdapterSpec instance + */ + public static JpaInboundChannelAdapterSpec inboundAdapter(JpaOperations jpaOperations) { + return inboundAdapter(new JpaExecutor(jpaOperations)); + } + + private static JpaInboundChannelAdapterSpec inboundAdapter(JpaExecutor jpaExecutor) { + return new JpaInboundChannelAdapterSpec(jpaExecutor); + } + + /** + * Create a {@link JpaUpdatingOutboundEndpointSpec} builder instance for one-way adapter + * based on the provided {@link EntityManagerFactory}. + * @param entityManagerFactory the {@link EntityManagerFactory} to use + * @return the JpaUpdatingOutboundEndpointSpec instance + */ + public static JpaUpdatingOutboundEndpointSpec outboundAdapter(EntityManagerFactory entityManagerFactory) { + return outboundAdapter(new JpaExecutor(entityManagerFactory)); + } + + /** + * Create a {@link JpaUpdatingOutboundEndpointSpec} builder instance for one-way adapter + * based on the provided {@link EntityManager}. + * @param entityManager the {@link EntityManager} to use + * @return the JpaUpdatingOutboundEndpointSpec instance + */ + public static JpaUpdatingOutboundEndpointSpec outboundAdapter(EntityManager entityManager) { + return outboundAdapter(new JpaExecutor(entityManager)); + } + + /** + * Create a {@link JpaUpdatingOutboundEndpointSpec} builder instance for one-way adapter + * based on the provided {@link JpaOperations}. + * @param jpaOperations the {@link JpaOperations} to use + * @return the JpaUpdatingOutboundEndpointSpec instance + */ + public static JpaUpdatingOutboundEndpointSpec outboundAdapter(JpaOperations jpaOperations) { + return outboundAdapter(new JpaExecutor(jpaOperations)); + } + + private static JpaUpdatingOutboundEndpointSpec outboundAdapter(JpaExecutor jpaExecutor) { + return new JpaUpdatingOutboundEndpointSpec(jpaExecutor) + .producesReply(false); + } + + /** + * Create a {@link JpaUpdatingOutboundEndpointSpec} builder instance for request-reply gateway + * based on the provided {@link EntityManagerFactory}. + * @param entityManagerFactory the {@link EntityManagerFactory} to use + * @return the JpaUpdatingOutboundEndpointSpec instance + */ + public static JpaUpdatingOutboundEndpointSpec updatingGateway(EntityManagerFactory entityManagerFactory) { + return updatingGateway(new JpaExecutor(entityManagerFactory)); + } + + /** + * Create a {@link JpaUpdatingOutboundEndpointSpec} builder instance for request-reply gateway + * based on the provided {@link EntityManager}. + * @param entityManager the {@link EntityManager} to use + * @return the JpaUpdatingOutboundEndpointSpec instance + */ + public static JpaUpdatingOutboundEndpointSpec updatingGateway(EntityManager entityManager) { + return updatingGateway(new JpaExecutor(entityManager)); + } + + /** + * Create a {@link JpaUpdatingOutboundEndpointSpec} builder instance for request-reply gateway + * based on the provided {@link JpaOperations}. + * @param jpaOperations the {@link JpaOperations} to use + * @return the JpaUpdatingOutboundEndpointSpec instance + */ + public static JpaUpdatingOutboundEndpointSpec updatingGateway(JpaOperations jpaOperations) { + return updatingGateway(new JpaExecutor(jpaOperations)); + } + + private static JpaUpdatingOutboundEndpointSpec updatingGateway(JpaExecutor jpaExecutor) { + return new JpaUpdatingOutboundEndpointSpec(jpaExecutor) + .producesReply(true); + } + + /** + * Create a {@link JpaRetrievingOutboundGatewaySpec} builder instance for request-reply gateway + * based on the provided {@link EntityManagerFactory}. + * @param entityManagerFactory the {@link EntityManagerFactory} to use + * @return the JpaRetrievingOutboundGatewaySpec instance + */ + public static JpaRetrievingOutboundGatewaySpec retrievingGateway(EntityManagerFactory entityManagerFactory) { + return retrievingGateway(new JpaExecutor(entityManagerFactory)); + } + + /** + * Create a {@link JpaRetrievingOutboundGatewaySpec} builder instance for request-reply gateway + * based on the provided {@link EntityManager}. + * @param entityManager the {@link EntityManager} to use + * @return the JpaRetrievingOutboundGatewaySpec instance + */ + public static JpaRetrievingOutboundGatewaySpec retrievingGateway(EntityManager entityManager) { + return retrievingGateway(new JpaExecutor(entityManager)); + } + + /** + * Create a {@link JpaRetrievingOutboundGatewaySpec} builder instance for request-reply gateway + * based on the provided {@link JpaOperations}. + * @param jpaOperations the {@link JpaOperations} to use + * @return the JpaRetrievingOutboundGatewaySpec instance + */ + public static JpaRetrievingOutboundGatewaySpec retrievingGateway(JpaOperations jpaOperations) { + return retrievingGateway(new JpaExecutor(jpaOperations)); + } + + private static JpaRetrievingOutboundGatewaySpec retrievingGateway(JpaExecutor jpaExecutor) { + return new JpaRetrievingOutboundGatewaySpec(jpaExecutor); + } + + private Jpa() { + super(); + } + +} diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/dsl/JpaBaseOutboundEndpointSpec.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/dsl/JpaBaseOutboundEndpointSpec.java new file mode 100644 index 0000000000..123d6ef500 --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/dsl/JpaBaseOutboundEndpointSpec.java @@ -0,0 +1,167 @@ +/* + * Copyright 2016 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.integration.jpa.dsl; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import org.springframework.integration.dsl.ComponentsRegistration; +import org.springframework.integration.dsl.MessageHandlerSpec; +import org.springframework.integration.jpa.core.JpaExecutor; +import org.springframework.integration.jpa.outbound.JpaOutboundGateway; +import org.springframework.integration.jpa.support.JpaParameter; +import org.springframework.integration.jpa.support.parametersource.BeanPropertyParameterSourceFactory; +import org.springframework.integration.jpa.support.parametersource.ParameterSourceFactory; + +/** + * The base {@link MessageHandlerSpec} for JPA Outbound endpoints. + * + * @param the target {@link JpaBaseOutboundEndpointSpec} implementation type. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public abstract class JpaBaseOutboundEndpointSpec> + extends MessageHandlerSpec + implements ComponentsRegistration { + + private final List jpaParameters = new LinkedList<>(); + + protected final JpaExecutor jpaExecutor; + + protected JpaBaseOutboundEndpointSpec(JpaExecutor jpaExecutor) { + this.jpaExecutor = jpaExecutor; + this.jpaExecutor.setJpaParameters(this.jpaParameters); + this.target = new JpaOutboundGateway(this.jpaExecutor); + } + + /** + * Specify the class type which is being used for retrieving entities from the database. + * @param entityClass the entity {@link Class} to use + * @return the spec + */ + public S entityClass(Class entityClass) { + this.jpaExecutor.setEntityClass(entityClass); + return _this(); + } + + /** + * Specify a JPA query to perform persistent operation. + * @param jpaQuery the JPA query to use. + * @return the spec + */ + public S jpaQuery(String jpaQuery) { + this.jpaExecutor.setJpaQuery(jpaQuery); + return _this(); + } + + /** + * Specify a native SQL query to perform persistent operation. + * @param nativeQuery the native SQL query to use. + * @return the spec + */ + public S nativeQuery(String nativeQuery) { + this.jpaExecutor.setNativeQuery(nativeQuery); + return _this(); + } + + /** + * Specify a name a named JPQL based query or a native SQL query. + * @param namedQuery the name of the pre-configured query. + * @return the spec + */ + public S namedQuery(String namedQuery) { + this.jpaExecutor.setNamedQuery(namedQuery); + return _this(); + } + + /** + * Specify a {@link ParameterSourceFactory} to populate query parameters at runtime against request message. + * @param parameterSourceFactory the {@link ParameterSourceFactory} to use. + * @return the spec + */ + public S parameterSourceFactory(ParameterSourceFactory parameterSourceFactory) { + this.jpaExecutor.setParameterSourceFactory(parameterSourceFactory); + return _this(); + } + + /** + * Add a value for indexed query parameter. + * @param value the value for query parameter by index + * @return the spec + */ + public S parameter(Object value) { + return parameter(new JpaParameter(value, null)); + } + + /** + * Add a value for named parameter in the query. + * @param name the name of the query parameter + * @param value the value for query parameter by name + * @return the spec + */ + public S parameter(String name, Object value) { + return parameter(new JpaParameter(name, value, null)); + } + + /** + * Add a SpEL expression for indexed parameter in the query. + * @param expression the SpEL expression for query parameter by index + * @return the spec + */ + public S parameterExpression(String expression) { + return parameter(new JpaParameter(null, expression)); + } + + /** + * Add a SpEL expression for named parameter in the query. + * @param name the name of the query parameter + * @param expression the SpEL expression for query parameter by name + * @return the spec + */ + public S parameterExpression(String name, String expression) { + return parameter(new JpaParameter(name, null, expression)); + } + + public S parameter(JpaParameter jpaParameter) { + this.jpaParameters.add(jpaParameter); + return _this(); + } + + /** + * Indicates that whether only the payload of the passed in {@code Message} + * will be used as a source of parameters. The is 'true' by default because as a + * default a {@link BeanPropertyParameterSourceFactory} implementation is + * used for the sqlParameterSourceFactory property. + * @param usePayloadAsParameterSource the {@code boolean} flag to indicate + * if use {@code payload} as a source of parameter values or not. + * @return the spec + */ + public S usePayloadAsParameterSource(Boolean usePayloadAsParameterSource) { + this.jpaExecutor.setUsePayloadAsParameterSource(usePayloadAsParameterSource); + return _this(); + } + + @Override + public Collection getComponentsToRegister() { + return Collections.singletonList(this.jpaExecutor); + } + +} diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/dsl/JpaInboundChannelAdapterSpec.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/dsl/JpaInboundChannelAdapterSpec.java new file mode 100644 index 0000000000..23c91e08d9 --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/dsl/JpaInboundChannelAdapterSpec.java @@ -0,0 +1,190 @@ +/* + * Copyright 2016 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.integration.jpa.dsl; + +import java.util.Collection; +import java.util.Collections; + +import org.springframework.expression.Expression; +import org.springframework.integration.dsl.ComponentsRegistration; +import org.springframework.integration.dsl.MessageSourceSpec; +import org.springframework.integration.expression.ValueExpression; +import org.springframework.integration.jpa.core.JpaExecutor; +import org.springframework.integration.jpa.inbound.JpaPollingChannelAdapter; +import org.springframework.integration.jpa.support.parametersource.ParameterSource; + +/** + * A {@link MessageSourceSpec} for a {@link JpaPollingChannelAdapter}. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public class JpaInboundChannelAdapterSpec + extends MessageSourceSpec + implements ComponentsRegistration { + + private final JpaExecutor jpaExecutor; + + JpaInboundChannelAdapterSpec(JpaExecutor jpaExecutor) { + this.jpaExecutor = jpaExecutor; + this.target = new JpaPollingChannelAdapter(this.jpaExecutor); + } + + /** + * Specify the class type which is being used for retrieving entities from the database. + * @param entityClass the entity {@link Class} to use + * @return the spec + */ + public JpaInboundChannelAdapterSpec entityClass(Class entityClass) { + this.jpaExecutor.setEntityClass(entityClass); + return this; + } + + /** + * Specify a JPA query to perform persistent operation. + * @param jpaQuery the JPA query to use. + * @return the spec + */ + public JpaInboundChannelAdapterSpec jpaQuery(String jpaQuery) { + this.jpaExecutor.setJpaQuery(jpaQuery); + return this; + } + + /** + * Specify a native SQL query to perform persistent operation. + * @param nativeQuery the native SQL query to use. + * @return the spec + */ + public JpaInboundChannelAdapterSpec nativeQuery(String nativeQuery) { + this.jpaExecutor.setNativeQuery(nativeQuery); + return this; + } + + /** + * Specify a name a named JPQL based query or a native SQL query. + * @param namedQuery the name of the pre-configured query. + * @return the spec + */ + public JpaInboundChannelAdapterSpec namedQuery(String namedQuery) { + this.jpaExecutor.setNamedQuery(namedQuery); + return this; + } + + /** + * If set to 'true', the retrieved objects are deleted from the database upon + * being polled. May not work in all situations, e.g. for Native SQL Queries. + * @param deleteAfterPoll Defaults to 'false'. + * @return the spec + */ + public JpaInboundChannelAdapterSpec deleteAfterPoll(boolean deleteAfterPoll) { + this.jpaExecutor.setDeleteAfterPoll(deleteAfterPoll); + return this; + } + + /** + * If not set, this property defaults to false, which means that + * deletion occurs on a per object basis if a collection of entities is being + * deleted. + *

If set to 'true' the elements of the payload are deleted as a batch + * operation. Be aware that this exhibits issues in regards to cascaded deletes. + *

The specification 'JSR 317: Java Persistence API, Version 2.0' does not + * support cascaded deletes in batch operations. The specification states in + * chapter 4.10: + *

"A delete operation only applies to entities of the specified class and + * its subclasses. It does not cascade to related entities." + * @param deleteInBatch Defaults to 'false' if not set. + * @return the spec + */ + public JpaInboundChannelAdapterSpec deleteInBatch(boolean deleteInBatch) { + this.jpaExecutor.setDeleteInBatch(deleteInBatch); + return this; + } + + /** + * If set to {@code true} the {@link javax.persistence.EntityManager#flush()} will be called + * after persistence operation. + * Has the same effect, if the {@code flushSize} is specified to {@code 1}. + * For convenience in cases when the provided entity to persist is not an instance of {@link Iterable}. + * @param flush defaults to 'false'. + * @return the spec + */ + public JpaInboundChannelAdapterSpec flushAfterDelete(boolean flush) { + this.jpaExecutor.setFlush(flush); + return this; + } + + /** + * Specify a {@link ParameterSource} that would be used to provide additional parameters. + * @param parameterSource the {@link ParameterSource} to use. + * @return the spec + */ + public JpaInboundChannelAdapterSpec parameterSource(ParameterSource parameterSource) { + this.jpaExecutor.setParameterSource(parameterSource); + return this; + } + + /** + * This parameter indicates that only one result object shall be returned as + * a result from the executed JPA operation. If set to true and + * the result list from the JPA operations contains only 1 element, then that + * 1 element is extracted and returned as payload. + * @param expectSingleResult true if a single object is expected. + * @return the spec + */ + public JpaInboundChannelAdapterSpec expectSingleResult(boolean expectSingleResult) { + this.jpaExecutor.setExpectSingleResult(expectSingleResult); + return this; + } + + /** + * Set the maximum number of results expression. It has be a non null value + * Not setting one will default to the behavior of fetching all the records + * @param maxResults the maximum number of results to retrieve + * @return the spec + */ + public JpaInboundChannelAdapterSpec maxResults(int maxResults) { + return maxResultsExpression(new ValueExpression<>(maxResults)); + } + + /** + * Specify a SpEL expression for maximum number of results expression. + * Not setting one will default to the behavior of fetching all the records + * @param maxResultsExpression The maximum results expression. + * @return the spec + */ + public JpaInboundChannelAdapterSpec maxResultsExpression(String maxResultsExpression) { + return maxResultsExpression(PARSER.parseExpression(maxResultsExpression)); + } + + /** + * Specify a SpEL expression for maximum number of results expression. + * Not setting one will default to the behavior of fetching all the records + * @param maxResultsExpression The maximum results expression. + * @return the spec + */ + public JpaInboundChannelAdapterSpec maxResultsExpression(Expression maxResultsExpression) { + this.jpaExecutor.setMaxResultsExpression(maxResultsExpression); + return this; + } + + @Override + public Collection getComponentsToRegister() { + return Collections.singletonList(this.jpaExecutor); + } + +} diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/dsl/JpaRetrievingOutboundGatewaySpec.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/dsl/JpaRetrievingOutboundGatewaySpec.java new file mode 100644 index 0000000000..1e236c410f --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/dsl/JpaRetrievingOutboundGatewaySpec.java @@ -0,0 +1,177 @@ +/* + * Copyright 2016 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.integration.jpa.dsl; + +import org.springframework.expression.Expression; +import org.springframework.integration.expression.ValueExpression; +import org.springframework.integration.jpa.core.JpaExecutor; +import org.springframework.integration.jpa.support.OutboundGatewayType; + +/** + * A {@link JpaBaseOutboundEndpointSpec} extension for the + * {@link org.springframework.integration.jpa.outbound.JpaOutboundGateway} with + * {@link org.springframework.integration.jpa.support.OutboundGatewayType#RETRIEVING} mode. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public class JpaRetrievingOutboundGatewaySpec extends JpaBaseOutboundEndpointSpec { + + JpaRetrievingOutboundGatewaySpec(JpaExecutor jpaExecutor) { + super(jpaExecutor); + this.target.setGatewayType(OutboundGatewayType.RETRIEVING); + this.target.setRequiresReply(true); + } + + /** + * This parameter indicates that only one result object shall be returned as + * a result from the executed JPA operation. If set to true and + * the result list from the JPA operations contains only 1 element, then that + * 1 element is extracted and returned as payload. + * @param expectSingleResult true if a single object is expected. + * @return the spec + */ + public JpaRetrievingOutboundGatewaySpec expectSingleResult(boolean expectSingleResult) { + this.jpaExecutor.setExpectSingleResult(expectSingleResult); + return this; + } + + /** + * Specify a first result in the query executed. + * @param firstResult the first result to use. + * @return the spec + */ + public JpaRetrievingOutboundGatewaySpec firstResult(int firstResult) { + return firstResultExpression(new ValueExpression<>(firstResult)); + } + + /** + * Specify a SpEL expression that will be evaluated to get the first result in the query executed. + * @param firstResultExpression The first result expression. + * @return the spec + */ + public JpaRetrievingOutboundGatewaySpec firstResultExpression(String firstResultExpression) { + return firstResultExpression(PARSER.parseExpression(firstResultExpression)); + } + + /** + * Specify a SpEL expression that will be evaluated to get the first result in the query executed. + * @param firstResultExpression The first result expression. + * @return the spec + */ + public JpaRetrievingOutboundGatewaySpec firstResultExpression(Expression firstResultExpression) { + this.jpaExecutor.setFirstResultExpression(firstResultExpression); + return this; + } + + /** + * Specify a SpEL expression that will be evaluated to get the {@code primaryKey} for + * {@link javax.persistence.EntityManager#find(Class, Object)} + * @param idExpression the SpEL expression for entity {@code primaryKey}. + * @return the spec + */ + public JpaRetrievingOutboundGatewaySpec idExpression(String idExpression) { + return idExpression(PARSER.parseExpression(idExpression)); + } + + /** + * Specify a SpEL expression that will be evaluated to get the {@code primaryKey} for + * {@link javax.persistence.EntityManager#find(Class, Object)} + * @param idExpression the SpEL expression for entity {@code primaryKey}. + * @return the spec + */ + public JpaRetrievingOutboundGatewaySpec idExpression(Expression idExpression) { + this.jpaExecutor.setIdExpression(idExpression); + return this; + } + + /** + * Set the maximum number of results expression. It has be a non null value + * Not setting one will default to the behavior of fetching all the records + * @param maxResults the maximum number of results to retrieve + * @return the spec + */ + public JpaRetrievingOutboundGatewaySpec maxResults(int maxResults) { + return maxResultsExpression(new ValueExpression<>(maxResults)); + } + + /** + * Specify a SpEL expression for maximum number of results expression. + * Not setting one will default to the behavior of fetching all the records + * @param maxResultsExpression The maximum results expression. + * @return the spec + */ + public JpaRetrievingOutboundGatewaySpec maxResultsExpression(String maxResultsExpression) { + return maxResultsExpression(PARSER.parseExpression(maxResultsExpression)); + } + + /** + * Specify a SpEL expression for maximum number of results expression. + * Not setting one will default to the behavior of fetching all the records + * @param maxResultsExpression The maximum results expression. + * @return the spec + */ + public JpaRetrievingOutboundGatewaySpec maxResultsExpression(Expression maxResultsExpression) { + this.jpaExecutor.setMaxResultsExpression(maxResultsExpression); + return this; + } + + /** + * If set to {@code true}, the retrieved objects are deleted from the database upon + * being polled. May not work in all situations, e.g. for Native SQL Queries. + * @param deleteAfterPoll defaults to {@code false}. + * @return the spec + */ + public JpaRetrievingOutboundGatewaySpec deleteAfterPoll(boolean deleteAfterPoll) { + this.jpaExecutor.setDeleteAfterPoll(deleteAfterPoll); + return this; + } + + /** + * If not set, this property defaults to false, which means that + * deletion occurs on a per object basis if a collection of entities is being + * deleted. + *

If set to 'true' the elements of the payload are deleted as a batch + * operation. Be aware that this exhibits issues in regards to cascaded deletes. + *

The specification 'JSR 317: Java Persistence API, Version 2.0' does not + * support cascaded deletes in batch operations. The specification states in + * chapter 4.10: + *

"A delete operation only applies to entities of the specified class and + * its subclasses. It does not cascade to related entities." + * @param deleteInBatch Defaults to 'false' if not set. + * @return the spec + */ + public JpaRetrievingOutboundGatewaySpec deleteInBatch(boolean deleteInBatch) { + this.jpaExecutor.setDeleteInBatch(deleteInBatch); + return this; + } + + /** + * If set to {@code true} the {@link javax.persistence.EntityManager#flush()} will be called + * after persistence operation. + * Has the same effect, if the {@code flushSize} is specified to {@code 1}. + * For convenience in cases when the provided entity to persist is not an instance of {@link Iterable}. + * @param flush defaults to 'false'. + * @return the spec + */ + public JpaRetrievingOutboundGatewaySpec flushAfterDelete(boolean flush) { + this.jpaExecutor.setFlush(flush); + return this; + } + +} diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/dsl/JpaUpdatingOutboundEndpointSpec.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/dsl/JpaUpdatingOutboundEndpointSpec.java new file mode 100644 index 0000000000..359f17ac5b --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/dsl/JpaUpdatingOutboundEndpointSpec.java @@ -0,0 +1,95 @@ +/* + * Copyright 2016 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.integration.jpa.dsl; + +import org.springframework.integration.jpa.core.JpaExecutor; +import org.springframework.integration.jpa.support.PersistMode; + +/** + * A {@link JpaBaseOutboundEndpointSpec} extension for the {@code updating} + * {@link org.springframework.integration.jpa.outbound.JpaOutboundGateway} mode. + * The {@code outbound-channel-adapter} is achievable through an internal {@code producesReply} option. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public class JpaUpdatingOutboundEndpointSpec extends JpaBaseOutboundEndpointSpec { + + JpaUpdatingOutboundEndpointSpec(JpaExecutor jpaExecutor) { + super(jpaExecutor); + } + + JpaUpdatingOutboundEndpointSpec producesReply(boolean producesReply) { + this.target.setProducesReply(producesReply); + if (producesReply) { + this.target.setRequiresReply(true); + } + return this; + } + + /** + * Specify a {@link PersistMode} for the gateway. + * Defaults to {@link PersistMode#MERGE}. + * @param persistMode the {@link PersistMode} to use. + * @return the spec + */ + public JpaUpdatingOutboundEndpointSpec persistMode(PersistMode persistMode) { + this.jpaExecutor.setPersistMode(persistMode); + return this; + } + + /** + * If set to {@code true} the {@link javax.persistence.EntityManager#flush()} will be called + * after persistence operation. + * Has the same effect, if the {@link #flushSize} is specified to {@code 1}. + * For convenience in cases when the provided entity to persist is not an instance of {@link Iterable}. + * @param flush defaults to {@code false}. + * @return the spec + */ + public JpaUpdatingOutboundEndpointSpec flush(boolean flush) { + this.jpaExecutor.setFlush(flush); + return this; + } + + /** + * If the provided value is greater than {@code 0}, then {@link javax.persistence.EntityManager#flush()} + * will be called after persistence operations as well as within batch operations. + * This property has precedence over the {@link #flush}, if it is specified to a value greater than {@code 0}. + * If the entity to persist is not an instance of {@link Iterable} and this property is greater than {@code 0}, + * then the entity will be flushed as if the {@link #flush} attribute was set to {@code true}. + * @param flushSize defaults to {@code 0}. + * @return the spec + */ + public JpaUpdatingOutboundEndpointSpec flushSize(int flushSize) { + this.jpaExecutor.setFlushSize(flushSize); + return this; + } + + /** + * If set to {@code true} the {@link javax.persistence.EntityManager#clear()} will be called, + * and only if the {@link javax.persistence.EntityManager#flush()} + * was called after performing persistence operations. + * @param clearOnFlush defaults to {@code false}. + * @return the spec + */ + public JpaUpdatingOutboundEndpointSpec clearOnFlush(boolean clearOnFlush) { + this.jpaExecutor.setClearOnFlush(clearOnFlush); + return this; + } + +} diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/dsl/package-info.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/dsl/package-info.java new file mode 100644 index 0000000000..b1c365ffc5 --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/dsl/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides JPA Components support for Java DSL. + */ +package org.springframework.integration.jpa.dsl; diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayFactoryBean.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayFactoryBean.java index 32577276ac..092efe4eab 100644 --- a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayFactoryBean.java +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayFactoryBean.java @@ -41,7 +41,7 @@ import org.springframework.transaction.interceptor.TransactionInterceptor; * @since 2.2 * */ -public class JpaOutboundGatewayFactoryBean extends AbstractFactoryBean { +public class JpaOutboundGatewayFactoryBean extends AbstractFactoryBean { private JpaExecutor jpaExecutor; @@ -120,7 +120,7 @@ public class JpaOutboundGatewayFactoryBean extends AbstractFactoryBean message = this.pollingResults.receive(10_000); + assertNotNull(message); + assertThat(message.getPayload(), instanceOf(StudentDomain.class)); + StudentDomain student = (StudentDomain) message.getPayload(); + assertEquals("First One", student.getFirstName()); + } + + @Test + public void testOutboundAdapterFlow() { + JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); + + List results1 = jdbcTemplate.queryForList("Select * from Student"); + assertNotNull(results1); + assertTrue(results1.size() == 3); + + Calendar dateOfBirth = Calendar.getInstance(); + dateOfBirth.set(1981, 9, 27); + + StudentDomain student = new StudentDomain() + .withFirstName("Artem") + .withLastName("Bilan") + .withGender(Gender.MALE) + .withDateOfBirth(dateOfBirth.getTime()) + .withLastUpdated(new Date()); + + assertNull(student.getRollNumber()); + + this.outboundAdapterFlowInput.send(MessageBuilder.withPayload(student).build()); + + List results2 = jdbcTemplate.queryForList("Select * from Student"); + assertNotNull(results2); + assertTrue(results2.size() == 4); + + assertNotNull(student.getRollNumber()); + } + + @Test + public void testUpdatingGatewayFlow() { + Calendar dateOfBirth = Calendar.getInstance(); + dateOfBirth.set(1981, 9, 27); + + StudentDomain student = new StudentDomain() + .withFirstName("Artem") + .withLastName("Bilan") + .withGender(Gender.MALE) + .withDateOfBirth(dateOfBirth.getTime()) + .withLastUpdated(new Date()); + + assertNull(student.getRollNumber()); + + this.updatingGatewayFlowInput.send(MessageBuilder.withPayload(student).build()); + + Message receive = this.persistResults.receive(10_000); + assertNotNull(receive); + + StudentDomain mergedStudent = (StudentDomain) receive.getPayload(); + assertEquals(student.getFirstName(), mergedStudent.getFirstName()); + assertNotNull(mergedStudent.getRollNumber()); + assertNull(student.getRollNumber()); + } + + @Test + public void testRetrievingGatewayFlow() { + this.retrievingGatewayFlowInput.send(MessageBuilder.withPayload(1002L).build()); + Message receive = this.retrieveResults.receive(10_000); + assertNotNull(receive); + assertThat(receive.getPayload(), instanceOf(StudentDomain.class)); + StudentDomain student = (StudentDomain) receive.getPayload(); + assertEquals("First Two", student.getFirstName()); + assertEquals(Gender.FEMALE, student.getGender()); + } + + + @Configuration + @EnableIntegration + public static class ContextConfiguration { + + @Bean + public JpaVendorAdapter jpaVendorAdapter() { + AbstractJpaVendorAdapter adapter = new HibernateJpaVendorAdapter(); + adapter.setShowSql(true); + adapter.setDatabase(Database.H2); + adapter.setGenerateDdl(true); + return adapter; + } + + @Bean + public LocalContainerEntityManagerFactoryBean entityManagerFactory() { + LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = + new LocalContainerEntityManagerFactoryBean(); + entityManagerFactoryBean.setDataSource(dataSource); + entityManagerFactoryBean.setPersistenceUnitName("persistenceUnit"); + entityManagerFactoryBean.setJpaVendorAdapter(jpaVendorAdapter()); + return entityManagerFactoryBean; + } + + @Bean + public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { + JpaTransactionManager jpaTransactionManager = new JpaTransactionManager(entityManagerFactory); + jpaTransactionManager.setDataSource(dataSource); + return jpaTransactionManager; + } + + @Bean + public IntegrationFlow pollingAdapterFlow(EntityManagerFactory entityManagerFactory) { + return IntegrationFlows + .from(Jpa.inboundAdapter(entityManagerFactory) + .entityClass(StudentDomain.class) + .maxResults(1) + .expectSingleResult(true), + e -> e.poller(p -> p.trigger(new OnlyOnceTrigger()))) + .channel(c -> c.queue("pollingResults")) + .get(); + } + + @Bean + public IntegrationFlow outboundAdapterFlow(EntityManagerFactory entityManagerFactory) { + return f -> f + .handle(Jpa.outboundAdapter(entityManagerFactory) + .entityClass(StudentDomain.class) + .persistMode(PersistMode.PERSIST), + e -> e.transactional(true)); + } + + @Bean + public IntegrationFlow updatingGatewayFlow(EntityManagerFactory entityManagerFactory) { + return f -> f + .handle(Jpa.updatingGateway(entityManagerFactory), + e -> e.transactional(true)) + .channel(c -> c.queue("persistResults")); + } + + @Bean + public IntegrationFlow retrievingGatewayFlow(EntityManagerFactory entityManagerFactory) { + return f -> f + .handle(Jpa.retrievingGateway(entityManagerFactory) + .jpaQuery("from Student s where s.id = :id") + .expectSingleResult(true) + .parameterExpression("id", "payload")) + .channel(c -> c.queue("retrieveResults")); + } + + } + +}