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:
committed by
Gary Russell
parent
4180e8b4f2
commit
67730726e9
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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><int-jpa:retrieving-outbound-gateway></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>
|
||||
|
||||
Reference in New Issue
Block a user