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 @@ + + + + A SpEL expression to determine the 'primaryKey' value for + 'EntityManager.find(Class entityClass, Object primaryKey)' using + the request Message as the root object of evaluation context. + The 'entityClass' argument is determined from the 'entity-class' attribute, + if present, otherwise the 'payload' class is used. All other attributes + (except 'delete-after-poll' and 'flush-after-delete') are disallowed + when specifying an 'id-expression'. + + + @@ -447,20 +460,13 @@ - - - - [DEPRECATED] Use 'max-results' instead. - - - Specifies the maximum number of entities that shall be returned by a JPA Operation. Using this attribute sets the 'maxResults' property of the JPA Query object. This attribute is mutually - exclusive with 'max-results-expression' (and the deprecated 'max-number-of-results') attributes. + exclusive with 'max-results-expression' attribute. @@ -474,7 +480,7 @@ - + - + Once entities have been retrieved from the database, shall @@ -513,7 +519,7 @@ - + If you want to automatically remove retrieved entities from @@ -528,7 +534,7 @@ - + Specifies whether EntityManager.flush() should be called after performing 'deletes' for retrieved entities. diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaInvalidOutboundGatewayParserTests.xml b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaInvalidOutboundGatewayParserTests.xml index 8f0e8fec1e..6f76d1a032 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaInvalidOutboundGatewayParserTests.xml +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaInvalidOutboundGatewayParserTests.xml @@ -19,7 +19,7 @@ auto-startup="true" entity-class="org.springframework.integration.jpa.test.entity.StudentDomain" order="1" - max-number-of-results="55" + max-results="55" first-result="1" first-result-expression="header['firstResult']" request-channel="in" diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.xml b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.xml index c350f3eb5c..fb5b08040b 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.xml +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.xml @@ -19,7 +19,7 @@ auto-startup="true" entity-class="org.springframework.integration.jpa.test.entity.StudentDomain" order="1" - max-number-of-results="55" + max-results="55" delete-after-poll="true" flush-after-delete="true" request-channel="in" @@ -58,7 +58,7 @@ entity-class="org.springframework.integration.jpa.test.entity.StudentDomain" order="1" first-result-expression="header['firstResult']" - max-number-of-results="55" + max-results="55" request-channel="in" reply-channel="out" reply-timeout="100" diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayIntegrationTests-context.xml b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayIntegrationTests-context.xml index 5537c66ab3..50b7752e6b 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayIntegrationTests-context.xml +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayIntegrationTests-context.xml @@ -11,6 +11,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayIntegrationTests.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayIntegrationTests.java index 02995795b8..3682c39689 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayIntegrationTests.java +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -15,22 +15,30 @@ */ package org.springframework.integration.jpa.outbound; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; import java.util.List; import javax.persistence.EntityManager; +import org.hamcrest.Matchers; +import org.hibernate.TypeMismatchException; +import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.integration.handler.ReplyRequiredException; +import org.springframework.integration.jpa.test.entity.StudentDomain; import org.springframework.integration.support.MessageBuilder; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHandler; +import org.springframework.messaging.MessageHandlingException; import org.springframework.messaging.MessagingException; +import org.springframework.messaging.PollableChannel; import org.springframework.messaging.SubscribableChannel; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -39,11 +47,13 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; * the components integrated. * * @author Amol Nayak + * @author Artem Bilan * @since 3.0 * */ @ContextConfiguration @RunWith(SpringJUnit4ClassRunner.class) +@DirtiesContext public class JpaOutboundGatewayIntegrationTests { @Autowired @@ -54,9 +64,38 @@ public class JpaOutboundGatewayIntegrationTests { @Qualifier("out") private SubscribableChannel responseChannel; + @Autowired + @Qualifier("findByEntityClass") + private SubscribableChannel findByEntityClassChannel; + + @Autowired + @Qualifier("findByPayloadType") + private SubscribableChannel findByPayloadTypeChannel; + + @Autowired + @Qualifier("findAndDelete") + private SubscribableChannel findAndDeleteChannel; + + @Autowired + @Qualifier("findResultChannel") + private PollableChannel findResultChannel; + + @Autowired + @Qualifier("invalidIdType") + private SubscribableChannel invalidIdTypeChannel; + @Autowired private EntityManager entityManager; + private volatile MessageHandler handler; + + @After + public void tearDown() { + if (this.handler != null) { + responseChannel.unsubscribe(this.handler); + } + } + /** * Sends a message with the payload as a integer representing the start number in the result * set and a header with value maxResults to get the max number of results @@ -64,18 +103,86 @@ public class JpaOutboundGatewayIntegrationTests { */ @Test public void retrieveFromSecondRecordAndMaximumOneRecord() throws Exception { - responseChannel.subscribe(new MessageHandler() { - @SuppressWarnings("rawtypes") + this.handler = new MessageHandler() { @Override public void handleMessage(Message message) throws MessagingException { - assertEquals(2, ((List) message.getPayload()).size()); + assertEquals(2, ((List) message.getPayload()).size()); assertEquals(1, entityManager.createQuery("from Student").getResultList().size()); } - }); + }; + this.responseChannel.subscribe(this.handler); + Message message = MessageBuilder .withPayload(1) .setHeader("maxResults", "10") .build(); - requestChannel.send(message); + this.requestChannel.send(message); } + + @Test + public void testFindByEntityClass() throws Exception { + this.handler = new MessageHandler() { + @Override + public void handleMessage(Message message) throws MessagingException { + assertThat(message.getPayload(), Matchers.instanceOf(StudentDomain.class)); + StudentDomain student = (StudentDomain) message.getPayload(); + assertEquals("First One", student.getFirstName()); + } + }; + this.responseChannel.subscribe(this.handler); + + Message message = MessageBuilder.withPayload(1001L).build(); + this.findByEntityClassChannel.send(message); + } + + @Test + public void testFindByPayloadType() throws Exception { + this.handler = new MessageHandler() { + @Override + public void handleMessage(Message message) throws MessagingException { + assertThat(message.getPayload(), Matchers.instanceOf(StudentDomain.class)); + StudentDomain student = (StudentDomain) message.getPayload(); + assertEquals("First Two", student.getFirstName()); + } + }; + this.responseChannel.subscribe(this.handler); + + StudentDomain payload = new StudentDomain(); + payload.setRollNumber(1002L); + Message message = MessageBuilder.withPayload(payload).build(); + this.findByPayloadTypeChannel.send(message); + } + + @Test + public void testFindAndDelete() throws Exception { + Message message = MessageBuilder.withPayload(1001L).build(); + this.findAndDeleteChannel.send(message); + + Message receive = this.findResultChannel.receive(2000); + assertNotNull(receive); + + try { + this.findAndDeleteChannel.send(message); + } + catch (Exception e) { + assertThat(e, Matchers.instanceOf(ReplyRequiredException.class)); + } + } + + @Test + public void testInvalidIdType() throws Exception { + Message message = MessageBuilder.withPayload(1).build(); + try { + this.invalidIdTypeChannel.send(message); + fail("PersistenceException expected"); + } + catch (Exception e) { + assertThat(e, Matchers.instanceOf(MessageHandlingException.class)); + assertThat(e.getCause(), Matchers.instanceOf(IllegalArgumentException.class)); + assertThat(e.getCause().getCause(), Matchers.instanceOf(TypeMismatchException.class)); + assertThat(e.getCause().getMessage(), + Matchers.containsString("Expected: class java.lang.Long, got class java.lang.Integer")); + } + } + } diff --git a/src/reference/docbook/jpa.xml b/src/reference/docbook/jpa.xml index cab0c8a85c..ca42ccdfde 100644 --- a/src/reference/docbook/jpa.xml +++ b/src/reference/docbook/jpa.xml @@ -1199,6 +1199,7 @@ public class Student { delete-after-poll="false" delete-in-batch="false" entity-class="" + id-expression="" ]]> ]]> + + + (Since Spring Integration 4.0) The SpEL expression to determine the + 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. Optional. + + A boolean flag indicating whether the select operation is expected to return a single result or a List of results. If this flag is set to 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 List of entities is being sent as the payload of the message. - By default the value is false.Optional. + By default the value is false. Optional. @@ -1381,6 +1393,20 @@ public class Student { ]]> + Retrieving an Entity using id-expression + + The following examples uses a Retrieving Outbound Gateway + together with 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. + + + ]]> + Update using a Named Query Using a Named Query is basically the same as using a JPQL query diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index 17fbeaaec4..16bcea0b0f 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -332,5 +332,14 @@ See for more information. +

+ JPA Retrieving Gateway: id-expression + + The id-expression attribute has been introduced for + <int-jpa:retrieving-outbound-gateway> to perform + EntityManager.find(Class entityClass, Object primaryKey). + See for more information. + +