INT-2879: JPA Retrieving: Add id-expression

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

* Add `id-expression` attribute for the `<int-jpa:retrieving-outbound-gateway>`
* Make it mutually exclusive with other `SELECT` attributes
* Remove deprecated `max-number-of-results`

INT-2879 Polishing

- Improve parser error message.
- Minor doc polish.

INT-2879: Support `delete-after-poll` and `flush`
This commit is contained in:
Artem Bilan
2014-04-22 14:27:58 +03:00
committed by Gary Russell
parent 4180e8b4f2
commit 67730726e9
9 changed files with 311 additions and 108 deletions

View File

@@ -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");

View File

@@ -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
*<p>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
* <i>requestMessage</i> 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 <code>false</code>, 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
*<p>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
*<p>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
*<p>"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 <code>true</code> 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
* <p>If the result map contains more than 1 element and
* {@link JpaExecutor#expectSingleResult} is <code>true</code>, then a
* {@link MessagingException} is thrown.
*
* If set to <code>false</code>, the complete result list is returned as the
* <p>If set to <code>false</code>, 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

View File

@@ -245,6 +245,19 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="id-expression">
<xsd:annotation>
<xsd:documentation>
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'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attributeGroup ref="coreJpaComponentAttributes" />
<xsd:attributeGroup ref="commonJpaOutboundGatewayAttributes"/>
<xsd:attributeGroup ref="commonRetrievingJpaAttributes" />
@@ -447,20 +460,13 @@
</xsd:attributeGroup>
<xsd:attributeGroup name="commonRetrievingJpaAttributes">
<xsd:attribute name="max-number-of-results">
<xsd:annotation>
<xsd:documentation>
[DEPRECATED] Use 'max-results' instead.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="max-results">
<xsd:annotation>
<xsd:documentation>
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.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
@@ -474,7 +480,7 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="expect-single-result" default="false">
<xsd:attribute name="expect-single-result">
<xsd:annotation>
<xsd:documentation><![CDATA[
This parameter indicates that only one result object shall be
@@ -497,7 +503,7 @@
<xsd:union memberTypes="xsd:boolean xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="delete-after-poll" default="false" use="optional">
<xsd:attribute name="delete-after-poll" use="optional">
<xsd:annotation>
<xsd:documentation>
Once entities have been retrieved from the database, shall
@@ -513,7 +519,7 @@
<xsd:union memberTypes="xsd:boolean xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="delete-in-batch" default="false" use="optional">
<xsd:attribute name="delete-in-batch" use="optional">
<xsd:annotation>
<xsd:documentation>
If you want to automatically remove retrieved entities from
@@ -528,7 +534,7 @@
<xsd:union memberTypes="xsd:boolean xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="flush-after-delete" type="xsd:string" default="false">
<xsd:attribute name="flush-after-delete" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Specifies whether EntityManager.flush() should be called after performing 'deletes' for retrieved entities.

View File

@@ -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"

View File

@@ -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"

View File

@@ -11,6 +11,7 @@
<import resource="classpath:/hibernateJpa-context.xml"/>
<int:channel id="in"/>
<int:channel id="out"/>
<int-jpa:retrieving-outbound-gateway
@@ -27,4 +28,47 @@
reply-timeout="100">
<int-jpa:transactional/>
</int-jpa:retrieving-outbound-gateway>
<int:channel id="findByEntityClass"/>
<int-jpa:retrieving-outbound-gateway
entity-manager="entityManager"
entity-class="org.springframework.integration.jpa.test.entity.StudentDomain"
request-channel="findByEntityClass"
id-expression="payload"
reply-channel="out"/>
<int:channel id="findByPayloadType"/>
<int-jpa:retrieving-outbound-gateway
entity-manager="entityManager"
request-channel="findByPayloadType"
id-expression="payload.rollNumber"
reply-channel="out"/>
<int:channel id="findAndDelete"/>
<int:channel id="findResultChannel">
<int:queue/>
</int:channel>
<int-jpa:retrieving-outbound-gateway
entity-manager="entityManager"
request-channel="findAndDelete"
entity-class="org.springframework.integration.jpa.test.entity.StudentDomain"
id-expression="payload"
delete-after-poll="true"
requires-reply="true"
reply-channel="findResultChannel">
<int-jpa:transactional/>
</int-jpa:retrieving-outbound-gateway>
<int:channel id="invalidIdType"/>
<int-jpa:retrieving-outbound-gateway
entity-manager="entityManager"
request-channel="invalidIdType"
entity-class="org.springframework.integration.jpa.test.entity.StudentDomain"
id-expression="payload"/>
</beans>

View File

@@ -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<Integer> 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<Long> 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<StudentDomain> message = MessageBuilder.withPayload(payload).build();
this.findByPayloadTypeChannel.send(message);
}
@Test
public void testFindAndDelete() throws Exception {
Message<Long> 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<Integer> 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"));
}
}
}

View File

@@ -1199,6 +1199,7 @@ public class Student {
delete-after-poll="false"
delete-in-batch="false"
entity-class=""
id-expression="" ]]><co id="outGateIdExpression"/><![CDATA[
entity-manager=""
entity-manager-factory=""
expect-single-result="false" ]]><co id="outGateExpectSingleResult"/><![CDATA[
@@ -1224,13 +1225,24 @@ public class Student {
<int-jpa:parameter name="" expression=""/>
</int-jpa:retrieving-outbound-gateway>]]></programlisting>
<calloutlist>
<callout arearefs="outGateIdExpression">
<para>
(Since <emphasis>Spring Integration 4.0</emphasis>) The SpEL expression to determine the
<code>primaryKey</code> value for
<code>EntityManager.find(Class entityClass, Object primaryKey)</code> method against
the <code>requestMessage</code> as root object of evaluation context.
The <code>entityClass</code> argument is determined from <code>entity-class</code> attribute,
if presented, otherwise from <code>payload</code> class. All other attributed are disallowed
in case of <code>id-expression</code>. <emphasis>Optional</emphasis>.
</para>
</callout>
<callout arearefs="outGateExpectSingleResult">
<para>
A boolean flag indicating whether the select operation is expected to return a single result or a <classname>List</classname> of results.
If this flag is set to <code>true</code>, the single entity selected is sent as the payload
of the message. If multiple entities are returned, an exception is thrown.
If <code>false</code>, the <classname>List</classname> of entities is being sent as the payload of the message.
By default the value is <code>false</code>.<emphasis>Optional</emphasis>.
By default the value is <code>false</code>. <emphasis>Optional</emphasis>.
</para>
</callout>
<callout arearefs="outGateMaxNumOfResults">
@@ -1381,6 +1393,20 @@ public class Student {
<int-jpa:parameter name="lastName" expression="headers['lastName']"/>
</int-jpa:outbound-gateway>]]></programlisting>
<para><emphasis>Retrieving an Entity using id-expression</emphasis></para>
<para>
The following examples uses a <emphasis>Retrieving Outbound Gateway</emphasis>
together with <code>id-expression</code> to retrieve (find) one and only one entity from
the database. The <code>primaryKey</code> is the result of <code>id-expression</code>
evaluation. The <code>entityClass</code> is a class of Message <code>payload</code>.
</para>
<programlisting language="xml"><![CDATA[<int-jpa:retrieving-outbound-gateway
request-channel="retrievingGatewayReqChannel"
reply-channel="retrievingGatewayReplyChannel"
id-expression="payload.id"
entity-manager="em"/>]]></programlisting>
<para><emphasis>Update using a Named Query</emphasis></para>
<para>
Using a Named Query is basically the same as using a JPQL query

View File

@@ -332,5 +332,14 @@
See <xref linkend="outbound-twitter-update"/> for more information.
</para>
</section>
<section id="4.0-jpa-id-expression">
<title>JPA Retrieving Gateway: id-expression</title>
<para>
The <code>id-expression</code> attribute has been introduced for
<code>&lt;int-jpa:retrieving-outbound-gateway&gt;</code> to perform
<code>EntityManager.find(Class entityClass, Object primaryKey)</code>.
See <xref linkend="jpa-retrieving-outbound-gateway"/> for more information.
</para>
</section>
</section>
</chapter>