diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/RetrievingJpaOutboundGatewayParser.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/RetrievingJpaOutboundGatewayParser.java index 7d1e158ae9..96ae26f653 100644 --- a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/RetrievingJpaOutboundGatewayParser.java +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/RetrievingJpaOutboundGatewayParser.java @@ -19,11 +19,15 @@ import org.w3c.dom.Element; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.parsing.BeanComponentDefinition; +import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.ExpressionFactoryBean; import org.springframework.integration.config.xml.IntegrationNamespaceUtils; import org.springframework.integration.jpa.support.OutboundGatewayType; +import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; /** * The Parser for the Retrieving Jpa Outbound Gateway. @@ -48,22 +52,6 @@ public class RetrievingJpaOutboundGatewayParser extends AbstractJpaOutboundGatew jpaExecutorBuilder.addPropertyValue("firstResultExpression", firstResultExpression); } - String maxNumberOfResults = gatewayElement.getAttribute("max-number-of-results"); - boolean hasMaxNumberOfResults = StringUtils.hasText(maxNumberOfResults); - - String maxResults = gatewayElement.getAttribute("max-results"); - boolean hasMaxResults = StringUtils.hasText(maxResults); - - if (hasMaxNumberOfResults) { - parserContext.getReaderContext().warning("'max-number-of-results' is deprecated in favor of 'max-results'", gatewayElement); - if (hasMaxResults) { - parserContext.getReaderContext().error("'max-number-of-results' and 'max-results' are mutually exclusive", gatewayElement); - } - else { - gatewayElement.setAttribute("max-results", maxNumberOfResults); - } - } - BeanDefinition maxResultsExpression = IntegrationNamespaceUtils .createExpressionDefinitionFromValueOrExpression("max-results", "max-results-expression", parserContext, gatewayElement, false); @@ -71,6 +59,37 @@ public class RetrievingJpaOutboundGatewayParser extends AbstractJpaOutboundGatew jpaExecutorBuilder.addPropertyValue("maxResultsExpression", maxResultsExpression); } + String idExpression = gatewayElement.getAttribute("id-expression"); + if (StringUtils.hasText(idExpression)) { + String[] otherAttributes = {"jpa-query", "native-query", "named-query", "first-result", + "first-result-expression", "max-results", "max-results-expression", "delete-in-batch", + "expect-single-result", "parameter-source-factory", "use-payload-as-parameter-source"}; + StringBuilder others = new StringBuilder(); + for (String otherAttribute : otherAttributes) { + if (gatewayElement.hasAttribute(otherAttribute) && + StringUtils.hasText(gatewayElement.getAttribute(otherAttribute))) { + if (others.length() > 0) { + others.append(", "); + } + others.append(otherAttribute); + } + } + boolean childElementsExist = !CollectionUtils.isEmpty(DomUtils.getChildElementsByTagName(gatewayElement, + "parameter")); + if (others.length() > 0 || childElementsExist) { + parserContext.getReaderContext().error( + (others.length() == 0 ? "" : "'" + others.toString() + "' " + + (childElementsExist ? "and " : "")) + + (childElementsExist ? "child elements " : "") + + "not allowed with an 'id-expression' attribute.", + gatewayElement); + } + AbstractBeanDefinition idExpressionDef = BeanDefinitionBuilder.genericBeanDefinition(ExpressionFactoryBean.class) + .addConstructorArgValue(idExpression) + .getBeanDefinition(); + jpaExecutorBuilder.addPropertyValue("idExpression", idExpressionDef); + } + IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, gatewayElement, "delete-after-poll"); IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, gatewayElement, "flush-after-delete", "flush"); IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, gatewayElement, "delete-in-batch"); 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 e09ce195c2..87479b96ed 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 @@ -80,6 +80,8 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat private volatile Expression firstResultExpression; + private volatile Expression idExpression; + private volatile PersistMode persistMode = PersistMode.MERGE; private volatile ParameterSourceFactory parameterSourceFactory = null; @@ -112,7 +114,6 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat /** * Constructor taking an {@link EntityManagerFactory} from which the * {@link EntityManager} can be obtained. - * * @param entityManagerFactory Must not be null. */ public JpaExecutor(EntityManagerFactory entityManagerFactory) { @@ -127,7 +128,6 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat /** * Constructor taking an {@link EntityManager} directly. - * * @param entityManager Must not be null. */ public JpaExecutor(EntityManager entityManager) { @@ -143,9 +143,7 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat * If custom behavior is required a custom implementation of {@link JpaOperations} * can be passed in. The implementations themselves typically provide access * to the {@link EntityManager}. - * * See also {@link DefaultJpaOperations} and {@link AbstractJpaOperations}. - * * @param jpaOperations Must not be null. */ public JpaExecutor(JpaOperations jpaOperations) { @@ -159,10 +157,8 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat } /** - * - * Verifies and sets the parameters. E.g. initializes the to be used + * Verify and sets the parameters. E.g. initializes the to be used * {@link ParameterSourceFactory}. - * */ @Override public void afterPropertiesSet() { @@ -212,13 +208,11 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat } /** - * Executes the actual Jpa Operation. Call this method, if you need access to + * Execute the actual Jpa Operation. Call this method, if you need access to * process return values. This methods return a Map that contains either * the number of affected entities or the affected entity itself. - * - * Keep in mind that the number of entities effected by the operation may + *
Keep in mind that the number of entities effected by the operation may
* not necessarily correlate with the number of rows effected in the database.
- *
* @param message The message.
* @return Either the number of affected entities when using a JPQL query.
* When using a merge/persist the updated/inserted itself is returned.
@@ -273,43 +267,54 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
* {@link JpaExecutor#parameterSourceFactory}. If the
* requestMessage parameter is null then
* {@link JpaExecutor#parameterSource} is being used for providing query parameters.
- *
* @param requestMessage May be null.
* @return The payload object, which may be null.
*/
@SuppressWarnings("unchecked")
public Object poll(final Message> requestMessage) {
final Object payload;
- final List> result;
- int maxNumberOfResults = this.evaluateExpressionForNumericResult(requestMessage, this.maxResultsExpression);
- if (requestMessage == null) {
- result = this.doPoll(this.parameterSource, 0, maxNumberOfResults);
- }
- else {
- int firstResult = 0;
- if(firstResultExpression != null) {
- firstResult = this.getFirstResult(requestMessage);
+
+ if (this.idExpression != null) {
+ Object id = this.idExpression.getValue(this.evaluationContext, requestMessage);
+ Class> entityClass = this.entityClass;
+ if (entityClass == null) {
+ entityClass = requestMessage.getPayload().getClass();
}
- ParameterSource parameterSource = this.determineParameterSource(requestMessage);
- result = this.doPoll(parameterSource, firstResult, maxNumberOfResults);
- }
-
- if (result.isEmpty()) {
- payload = null;
+ payload = this.jpaOperations.find(entityClass, id);
}
else {
- if (this.expectSingleResult) {
- if (result.size() == 1) {
- payload = result.iterator().next();
- }
- else {
- throw new MessagingException(requestMessage,
- "The Jpa operation returned more than 1 result object but expectSingleResult was 'true'.");
- }
+ final List> result;
+ int maxNumberOfResults = this.evaluateExpressionForNumericResult(requestMessage, this.maxResultsExpression);
+ if (requestMessage == null) {
+ result = this.doPoll(this.parameterSource, 0, maxNumberOfResults);
}
else {
- payload = result;
+ int firstResult = 0;
+ if (firstResultExpression != null) {
+ firstResult = this.getFirstResult(requestMessage);
+ }
+ ParameterSource parameterSource = this.determineParameterSource(requestMessage);
+ result = this.doPoll(parameterSource, firstResult, maxNumberOfResults);
+ }
+
+ if (result.isEmpty()) {
+ payload = null;
+ }
+ else {
+
+ if (this.expectSingleResult) {
+ if (result.size() == 1) {
+ payload = result.iterator().next();
+ }
+ else {
+ throw new MessagingException(requestMessage,
+ "The Jpa operation returned more than 1 result object but expectSingleResult was 'true'.");
+ }
+ }
+ else {
+ payload = result;
+ }
}
}
@@ -379,7 +384,6 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
/**
* Execute the JPA operation. Delegates to {@link JpaExecutor#poll(Message)}.
- *
* @return The object or null.
*/
public Object poll() {
@@ -414,7 +418,6 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
/**
* Sets the class type which is being used for retrieving entities from the
* database.
- *
* @param entityClass Must not be null.
*/
public void setEntityClass(Class> entityClass) {
@@ -437,7 +440,6 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
* this property will allow you to use native SQL. Optionally you can also set
* the entityClass property at the same time. If specified the entityClass will
* be used as the result class for the native query.
- *
* @param nativeQuery The provided SQL query must neither be null nor empty.
*/
public void setNativeQuery(String nativeQuery) {
@@ -452,7 +454,6 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
/**
* A named query can either refer to a named JPQL based query or a native SQL
* query.
- *
* @param namedQuery Must neither be null nor empty
*/
public void setNamedQuery(String namedQuery) {
@@ -481,7 +482,6 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
* 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 'false'.
*/
public void setFlush(boolean flush) {
@@ -494,7 +494,6 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
* 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 '0'.
*/
public void setFlushSize(int flushSize) {
@@ -505,11 +504,9 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
/**
* 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 'false'.
* @see #setFlush(boolean)
* @see #setFlushSize(int)
- *
- * @param clearOnFlush defaults to 'false'.
*/
public void setClearOnFlush(boolean clearOnFlush) {
this.clearOnFlush = clearOnFlush;
@@ -519,17 +516,13 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
* 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
+ *
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 + *
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 + *
"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.
*/
public void setDeleteInBatch(boolean deleteInBatch) {
@@ -539,7 +532,6 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
/**
* 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'.
*/
public void setDeleteAfterPoll(boolean deleteAfterPoll) {
@@ -547,7 +539,6 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
}
/**
- *
* @param parameterSourceFactory Must not be null
*/
public void setParameterSourceFactory(ParameterSourceFactory parameterSourceFactory) {
@@ -556,9 +547,8 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
}
/**
- * Specifies the {@link ParameterSource} that would be used to provide
+ * Specify the {@link ParameterSource} that would be used to provide
* additional parameters.
- *
* @param parameterSource Must not be null.
*/
public void setParameterSource(ParameterSource parameterSource) {
@@ -572,14 +562,11 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
* 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.
- *
- * If the result map contains more than 1 element and
+ *
If the result map contains more than 1 element and
* {@link JpaExecutor#expectSingleResult} is true, then a
* {@link MessagingException} is thrown.
- *
- * If set to false, the complete result list is returned as the
+ *
If set to false, the complete result list is returned as the
* payload.
- *
* @param expectSingleResult true if a single object is expected.
*
*/
@@ -588,11 +575,9 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
}
/**
- * Sets the expression that will be evaluated to get the first result in the query executed.
+ * Set the expression that will be evaluated to get the first result in the query executed.
* If a null expression is set, all the results in the result set will be retrieved
- *
* @param firstResultExpression The first result expression.
- *
* @see Query#setFirstResult(int)
*/
public void setFirstResultExpression(Expression firstResultExpression) {
@@ -600,11 +585,21 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
}
+ /**
+ * 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.
+ * @since 4.0
+ */
+ public void setIdExpression(Expression idExpression) {
+ this.idExpression = idExpression;
+ }
+
+
/**
- * Sets the expression for maximum number of results expression. It has be a non null value
+ * 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
- *
* @param maxResultsExpression The maximum results expression.
*/
public void setMaxResultsExpression(Expression maxResultsExpression) {
@@ -615,9 +610,7 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
/**
* Set the max number of results to retrieve from the database. Defaults to
* 0, which means that all possible objects shall be retrieved.
- *
* @param maxNumberOfResults Must not be negative.
- *
* @see Query#setMaxResults(int)
*/
public void setMaxNumberOfResults(int maxNumberOfResults) {
@@ -627,7 +620,6 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
/**
* Sets the evaluation context for evaluating the expression to get the from record of the
* result set retrieved by the retrieving gateway.
- *
* @param evaluationContext The evaluation context.
*/
@Override
diff --git a/spring-integration-jpa/src/main/resources/org/springframework/integration/jpa/config/xml/spring-integration-jpa-4.0.xsd b/spring-integration-jpa/src/main/resources/org/springframework/integration/jpa/config/xml/spring-integration-jpa-4.0.xsd
index 829b175e3e..8507c91236 100644
--- a/spring-integration-jpa/src/main/resources/org/springframework/integration/jpa/config/xml/spring-integration-jpa-4.0.xsd
+++ b/spring-integration-jpa/src/main/resources/org/springframework/integration/jpa/config/xml/spring-integration-jpa-4.0.xsd
@@ -245,6 +245,19 @@
+ primaryKey value for
+ EntityManager.find(Class entityClass, Object primaryKey) method against
+ the requestMessage as root object of evaluation context.
+ The entityClass argument is determined from entity-class attribute,
+ if presented, otherwise from payload class. All other attributed are disallowed
+ in case of id-expression. true, the single entity selected is sent as the payload
of the message. If multiple entities are returned, an exception is thrown.
If false, the false.false. id-expression to retrieve (find) one and only one entity from
+ the database. The primaryKey is the result of id-expression
+ evaluation. The entityClass is a class of Message payload.
+ id-expression attribute has been introduced for
+ <int-jpa:retrieving-outbound-gateway> to perform
+ EntityManager.find(Class entityClass, Object primaryKey).
+ See