INT-3150: JPA: Add support for max-results-expression

JIRA: https://jira.springsource.org/browse/INT-3150
This commit is contained in:
Amol Nayak
2013-10-02 23:33:18 +03:00
committed by Artem Bilan
parent fb186d3dd5
commit bd2cde4202
19 changed files with 314 additions and 132 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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,6 +15,8 @@
*/
package org.springframework.integration.jpa.config.xml;
import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
@@ -23,20 +25,17 @@ import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.jpa.inbound.JpaPollingChannelAdapter;
import org.w3c.dom.Element;
/**
* The JPA Inbound Channel adapter parser
*
* @author Amol Nayak
* @author Gunnar Hillert
*
* @since 2.2
*
*/
public class JpaInboundChannelAdapterParser extends AbstractPollingInboundChannelAdapterParser{
public class JpaInboundChannelAdapterParser extends AbstractPollingInboundChannelAdapterParser {
@Override
protected BeanMetadataElement parseSource(Element element, ParserContext parserContext) {
final BeanDefinitionBuilder jpaPollingChannelAdapterBuilder = BeanDefinitionBuilder
@@ -44,7 +43,12 @@ public class JpaInboundChannelAdapterParser extends AbstractPollingInboundChanne
final BeanDefinitionBuilder jpaExecutorBuilder = JpaParserUtils.getJpaExecutorBuilder(element, parserContext);
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, element, "max-number-of-results");
BeanDefinition definition = IntegrationNamespaceUtils
.createExpressionDefinitionFromValueOrExpression("max-number-of-results", "max-results-expression",
parserContext, element, false);
if (definition != null) {
jpaExecutorBuilder.addPropertyValue("maxResultsExpression", definition);
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, element, "delete-after-poll");
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, element, "delete-in-batch");
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, element, "expect-single-result");
@@ -59,4 +63,5 @@ public class JpaInboundChannelAdapterParser extends AbstractPollingInboundChanne
return jpaPollingChannelAdapterBuilder.getBeanDefinition();
}
}

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.integration.jpa.config.xml;
import static org.springframework.integration.config.xml.IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
@@ -31,9 +29,7 @@ import org.springframework.integration.jpa.support.OutboundGatewayType;
*
* @author Amol Nayak
* @author Gunnar Hillert
*
* @since 2.2
*
*/
public class RetrievingJpaOutboundGatewayParser extends AbstractJpaOutboundGatewayParser {
@@ -44,14 +40,20 @@ public class RetrievingJpaOutboundGatewayParser extends AbstractJpaOutboundGatew
final BeanDefinitionBuilder jpaExecutorBuilder = JpaParserUtils.getOutboundGatewayJpaExecutorBuilder(gatewayElement, parserContext);
BeanDefinition firstResultExpression = createExpressionDefinitionFromValueOrExpression("first-result",
"first-result-expression", parserContext, gatewayElement, false);
if(firstResultExpression != null) {
BeanDefinition firstResultExpression = IntegrationNamespaceUtils
.createExpressionDefinitionFromValueOrExpression("first-result", "first-result-expression",
parserContext, gatewayElement, false);
if (firstResultExpression != null) {
jpaExecutorBuilder.addPropertyValue("firstResultExpression", firstResultExpression);
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, gatewayElement, "max-number-of-results");
BeanDefinition maxResultsExpression = IntegrationNamespaceUtils
.createExpressionDefinitionFromValueOrExpression("max-number-of-results", "max-results-expression",
parserContext, gatewayElement, false);
if (maxResultsExpression != null) {
jpaExecutorBuilder.addPropertyValue("maxResultsExpression", maxResultsExpression);
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, gatewayElement, "delete-after-poll");
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, gatewayElement, "delete-in-batch");
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, gatewayElement, "expect-single-result");
@@ -64,6 +66,8 @@ public class RetrievingJpaOutboundGatewayParser extends AbstractJpaOutboundGatew
jpaOutboundGatewayBuilder.addConstructorArgReference(jpaExecutorBeanName);
jpaOutboundGatewayBuilder.addPropertyValue("gatewayType", OutboundGatewayType.RETRIEVING);
return jpaOutboundGatewayBuilder;
}
}

View File

@@ -27,6 +27,7 @@ import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.integration.Message;
import org.springframework.integration.MessagingException;
import org.springframework.integration.expression.IntegrationEvaluationContextAware;
@@ -65,7 +66,8 @@ import org.springframework.util.Assert;
*/
public class JpaExecutor implements InitializingBean, BeanFactoryAware, IntegrationEvaluationContextAware {
private volatile JpaOperations jpaOperations;
private final JpaOperations jpaOperations;
private volatile List<JpaParameter> jpaParameters;
private volatile Class<?> entityClass;
@@ -73,8 +75,7 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
private volatile String nativeQuery;
private volatile String namedQuery;
/** 0 means all possible objects shall be retrieved. */
private volatile int maxNumberOfResults = 0;
private volatile Expression maxResultsExpression;
private volatile Expression firstResultExpression;
@@ -91,7 +92,7 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
/**
* Indicates that whether only the payload of the passed in {@link Message}
* will be used as a source of parameters. The is 'true' by default because as a
* default a {@link BeanPropertyJpaParameterSourceFactory} implementation is
* default a {@link BeanPropertyParameterSourceFactory} implementation is
* used for the sqlParameterSourceFactory property.
*/
private volatile Boolean usePayloadAsParameterSource = null;
@@ -163,7 +164,7 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
if (this.parameterSourceFactory == null) {
ExpressionEvaluatingParameterSourceFactory expressionSourceFactory =
new ExpressionEvaluatingParameterSourceFactory(this.beanFactory);
expressionSourceFactory.setParameters(jpaParameters);
expressionSourceFactory.setParameters(this.jpaParameters);
this.parameterSourceFactory = expressionSourceFactory;
}
@@ -192,9 +193,7 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
if (this.usePayloadAsParameterSource == null) {
this.usePayloadAsParameterSource = true;
}
}
}
/**
@@ -206,7 +205,8 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
* not necessarily correlate with the number of rows effected in the database.
*
* @param message
* @return Either the number of affected entities when using a JPAQL query. When using a merge/persist the updated/inserted itself is returned.
* @return Either the number of affected entities when using a JPQL query.
* When using a merge/persist the updated/inserted itself is returned.
*/
public Object executeOutboundJpaOperation(final Message<?> message) {
@@ -214,22 +214,16 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
ParameterSource parameterSource = null;
if (this.jpaQuery != null || this.nativeQuery != null || this.namedQuery != null) {
parameterSource = determineParameterSource(message);
parameterSource = this.determineParameterSource(message);
}
if (this.jpaQuery != null) {
result = this.jpaOperations.executeUpdate(this.jpaQuery, parameterSource);
}
else if (this.nativeQuery != null) {
result = this.jpaOperations.executeUpdateWithNativeQuery(this.nativeQuery, parameterSource);
}
else if (this.namedQuery != null) {
result = this.jpaOperations.executeUpdateWithNamedQuery(this.namedQuery, parameterSource);
}
else {
@@ -238,8 +232,7 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
result = message.getPayload();
}
else if (PersistMode.MERGE.equals(this.persistMode)) {
final Object mergedEntity = this.jpaOperations.merge(message.getPayload());
result = mergedEntity;
result = this.jpaOperations.merge(message.getPayload());
}
else if (PersistMode.DELETE.equals(this.persistMode)) {
this.jpaOperations.delete(message.getPayload());
@@ -270,16 +263,17 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
public Object poll(final Message<?> requestMessage) {
final Object payload;
final List<?> result;
int maxNumberOfResults = this.evaluateExpressionForNumericResult(requestMessage, this.maxResultsExpression);
if (requestMessage == null) {
result = doPoll(this.parameterSource, 0);
result = this.doPoll(this.parameterSource, 0, maxNumberOfResults);
}
else {
int firstResult = 0;
if(firstResultExpression != null) {
firstResult = getFirstResult(requestMessage);
firstResult = this.getFirstResult(requestMessage);
}
ParameterSource parameterSource = determineParameterSource(requestMessage);
result = doPoll(parameterSource, firstResult);
ParameterSource parameterSource = this.determineParameterSource(requestMessage);
result = this.doPoll(parameterSource, firstResult, maxNumberOfResults);
}
if (result.isEmpty()) {
@@ -293,8 +287,7 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
}
else {
throw new MessagingException(requestMessage,
"The Jpa operation returned more than "
+ "1 result object but expectSingleResult was 'true'.");
"The Jpa operation returned more than 1 result object but expectSingleResult was 'true'.");
}
}
else {
@@ -322,28 +315,34 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
}
private int getFirstResult(final Message<?> requestMessage) {
int firstResult = 0;
Object firstRecordEvaluationResult = firstResultExpression.getValue(evaluationContext, requestMessage);
if(firstRecordEvaluationResult != null) {
if(firstRecordEvaluationResult instanceof Number) {
firstResult = ((Number)firstRecordEvaluationResult).intValue();
}
else if(firstRecordEvaluationResult instanceof String){
try {
firstResult = Integer.parseInt((String)firstRecordEvaluationResult);
return this.evaluateExpressionForNumericResult(requestMessage, this.firstResultExpression);
}
private int evaluateExpressionForNumericResult(final Message<?> requestMessage, Expression expression) {
int evaluatedResult = 0;
if(expression != null) {
Object evaluationResult = expression.getValue(this.evaluationContext, requestMessage);
if(evaluationResult != null) {
if(evaluationResult instanceof Number) {
evaluatedResult = ((Number) evaluationResult).intValue();
}
catch (NumberFormatException e) {
throw new IllegalArgumentException(
"Value " + firstRecordEvaluationResult + " passed as firstRecord cannot be " +
"parsed to a number");
else if(evaluationResult instanceof String){
try {
evaluatedResult = Integer.parseInt((String) evaluationResult);
}
catch (NumberFormatException e) {
throw new IllegalArgumentException(
"Value " + evaluationResult + " passed as cannot be " +
"parsed to a number, expected to be numeric");
}
}
else {
throw new IllegalArgumentException("Expected the value to be a Number" +
" got " + evaluationResult.getClass().getName());
}
}
else {
throw new IllegalArgumentException("Expected the value of the firstRecord to be a Number" +
" got " + firstRecordEvaluationResult.getClass().getName());
}
}
return firstResult;
return evaluatedResult;
}
private ParameterSource determineParameterSource(final Message<?> requestMessage) {
@@ -361,26 +360,25 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
* Execute the JPA operation. Delegates to {@link JpaExecutor#poll(Message)}.
*/
public Object poll() {
return poll(null);
return this.poll(null);
}
protected List<?> doPoll(ParameterSource jpaQLParameterSource, int firstResult) {
protected List<?> doPoll(ParameterSource jpaQLParameterSource, int firstResult, int maxNumberOfResults) {
List<?> payload = null;
if (this.jpaQuery != null) {
payload = jpaOperations.getResultListForQuery(this.jpaQuery, jpaQLParameterSource,
payload = this.jpaOperations.getResultListForQuery(this.jpaQuery, jpaQLParameterSource,
firstResult, maxNumberOfResults);
}
else if (this.nativeQuery != null) {
payload = jpaOperations.getResultListForNativeQuery(this.nativeQuery, this.entityClass, jpaQLParameterSource,
payload = this.jpaOperations.getResultListForNativeQuery(this.nativeQuery, this.entityClass, jpaQLParameterSource,
firstResult, maxNumberOfResults);
}
else if (this.namedQuery != null) {
payload = jpaOperations.getResultListForNamedQuery(this.namedQuery, jpaQLParameterSource,
payload = this.jpaOperations.getResultListForNamedQuery(this.namedQuery, jpaQLParameterSource,
firstResult, maxNumberOfResults);
}
else if (this.entityClass != null) {
payload = jpaOperations.getResultListForClass(this.entityClass,
firstResult, maxNumberOfResults);
payload = this.jpaOperations.getResultListForClass(this.entityClass, firstResult, maxNumberOfResults);
}
else {
throw new IllegalStateException("For the polling operation, one of "
@@ -422,7 +420,7 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
public void setNativeQuery(String nativeQuery) {
Assert.isTrue(this.namedQuery == null && this.jpaQuery == null, "You can define only one of the "
+ "properties 'jpaQuery', 'nativeQuery', 'namedQuery'");;
+ "properties 'jpaQuery', 'nativeQuery', 'namedQuery'");
Assert.hasText(nativeQuery, "nativeQuery must neither be null nor empty.");
this.nativeQuery = nativeQuery;
@@ -490,8 +488,7 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
*
* @param parameterSourceFactory Must not be null
*/
public void setParameterSourceFactory(
ParameterSourceFactory parameterSourceFactory) {
public void setParameterSourceFactory(ParameterSourceFactory parameterSourceFactory) {
Assert.notNull(parameterSourceFactory, "parameterSourceFactory must not be null.");
this.parameterSourceFactory = parameterSourceFactory;
}
@@ -526,19 +523,6 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
this.expectSingleResult = expectSingleResult;
}
/**
* 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) {
Assert.isTrue(maxNumberOfResults >= 0, "maxNumberOfResults must not be negative.");
this.maxNumberOfResults = maxNumberOfResults;
}
/**
* Sets 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
@@ -552,6 +536,30 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
}
/**
* Sets 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
*/
public void setMaxResultsExpression(Expression maxResultsExpression) {
Assert.notNull(maxResultsExpression, "maxResultsExpression cannot be null");
this.maxResultsExpression = maxResultsExpression;
}
/**
* 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) {
this.setMaxResultsExpression(new LiteralExpression("" + maxNumberOfResults));
}
/**
* Sets the evaluation context for evaluating the expression to get the from record of the
* result set retrieved by the retrieving gateway.
@@ -559,8 +567,8 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat
* @param evaluationContext
*/
@Override
public void setIntegrationEvaluationContext(
EvaluationContext evaluationContext) {
public void setIntegrationEvaluationContext(EvaluationContext evaluationContext) {
this.evaluationContext = evaluationContext;
}
}

View File

@@ -132,7 +132,7 @@ public interface JpaOperations {
*
* @param query Must not be null or empty
* @param firstResult The first result
* @param maxNumberOfResults Must be a non-negative value
* @param maxNumberOfResults Must be a non-negative value, any negative or zero will be ignored
* @param source the Parameter source for this query to be executed, if none then set null
* @return List of found entities
*/

View File

@@ -419,10 +419,21 @@
<xsd:documentation>
Specifies the maximum number of entities that shall be returned
by a JPA Operation. Using this attribute you basically set
the 'maxResults' property of the JPA Query object.
the 'maxResults' property of the JPA Query object. This attribute is mutually
exclusive to max-results-expression attribute.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="max-results-expression">
<xsd:annotation>
<xsd:documentation>
Specifies the expression for the maximum number of entities that shall be returned
by a JPA Operation. Using this attribute you basically set
the 'maxResults' property of the JPA Query object. This attribute is mutually
exclusive to max-number-of-results attribute.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="expect-single-result" default="false">
<xsd:annotation>
<xsd:documentation><![CDATA[

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
@@ -19,8 +19,11 @@ import static org.junit.Assert.assertTrue;
import org.junit.After;
import org.junit.Test;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.integration.channel.AbstractMessageChannel;
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
import org.springframework.integration.jpa.core.JpaExecutor;
@@ -29,6 +32,8 @@ import org.springframework.integration.test.util.TestUtils;
/**
* @author Gunnar Hillert
* @author Amol Nayak
*
* @since 2.2
*
*/
@@ -84,10 +89,44 @@ public class JpaInboundChannelAdapterParserTests {
assertNotNull(jpaOperations);
assertEquals(Integer.valueOf(13), TestUtils.getPropertyValue(jpaExecutor, "maxNumberOfResults", Integer.class));
LiteralExpression expression = TestUtils.getPropertyValue(jpaExecutor, "maxResultsExpression", LiteralExpression.class);
assertNotNull(expression);
assertEquals("13", TestUtils.getPropertyValue(expression, "literalValue"));
}
@Test
public void testJpaInboundChannelAdapterParserWithMaxResultsExpression() throws Exception {
setUp("JpaInboundChannelAdapterParserTests.xml", getClass(), "jpaInboundChannelAdapter3");
final AbstractMessageChannel outputChannel = TestUtils.getPropertyValue(this.consumer, "outputChannel", AbstractMessageChannel.class);
assertEquals("out", outputChannel.getComponentName());
final JpaExecutor jpaExecutor = TestUtils.getPropertyValue(this.consumer, "source.jpaExecutor", JpaExecutor.class);
assertNotNull(jpaExecutor);
final Class<?> entityClass = TestUtils.getPropertyValue(jpaExecutor, "entityClass", Class.class);
assertEquals("org.springframework.integration.jpa.test.entity.StudentDomain", entityClass.getName());
final JpaOperations jpaOperations = TestUtils.getPropertyValue(jpaExecutor, "jpaOperations", JpaOperations.class);
assertNotNull(jpaOperations);
SpelExpression expression = TestUtils.getPropertyValue(jpaExecutor, "maxResultsExpression", SpelExpression.class);
assertNotNull(expression);
assertEquals("@maxNumberOfResults", TestUtils.getPropertyValue(expression, "expression"));
}
@Test
public void testJpaExecutorBeanIdNaming() throws Exception {

View File

@@ -28,5 +28,16 @@
channel="out">
<int:poller fixed-rate="5000"/>
</int-jpa:inbound-channel-adapter>
<int-jpa:inbound-channel-adapter id="jpaInboundChannelAdapter3"
entity-manager-factory="entityManagerFactory"
entity-class="org.springframework.integration.jpa.test.entity.StudentDomain"
max-results-expression="@maxNumberOfResults"
channel="out">
<int:poller fixed-rate="5000"/>
</int-jpa:inbound-channel-adapter>
<bean name="maxNumberOfResults" class="java.lang.Integer">
<constructor-arg value="2"/>
</bean>
</beans>

View File

@@ -76,8 +76,10 @@ public class JpaOutboundGatewayParserTests extends AbstractRequestHandlerAdvice
final JpaOperations jpaOperations = TestUtils.getPropertyValue(jpaExecutor, "jpaOperations", JpaOperations.class);
assertNotNull(jpaOperations);
assertTrue(TestUtils.getPropertyValue(jpaExecutor, "expectSingleResult", Boolean.class));
final Integer maxNumberOfResults = TestUtils.getPropertyValue(jpaExecutor, "maxNumberOfResults", Integer.class);
assertEquals(Integer.valueOf(55), maxNumberOfResults);
final LiteralExpression maxResultsExpression =
TestUtils.getPropertyValue(jpaExecutor, "maxResultsExpression", LiteralExpression.class);
assertNotNull(maxResultsExpression);
assertEquals("55", TestUtils.getPropertyValue(maxResultsExpression, "literalValue"));
}
@Test
@@ -102,6 +104,17 @@ public class JpaOutboundGatewayParserTests extends AbstractRequestHandlerAdvice
assertEquals("header['firstResult']", TestUtils.getPropertyValue(firstResultExpression, "expression", String.class));
}
@Test
public void testRetrievingJpaOutboundGatewayParserWithMaxResultExpression() throws Exception {
setUp("JpaOutboundGatewayParserTests.xml", getClass(), "retrievingJpaOutboundGatewayWithMaxResultExpression");
final JpaOutboundGateway jpaOutboundGateway = TestUtils.getPropertyValue(this.consumer, "handler", JpaOutboundGateway.class);
Expression maxNumberOfResultExpression =
TestUtils.getPropertyValue(jpaOutboundGateway, "jpaExecutor.maxResultsExpression", Expression.class);
assertNotNull(maxNumberOfResultExpression);
assertEquals(SpelExpression.class, maxNumberOfResultExpression.getClass());
assertEquals("header['maxResults']", TestUtils.getPropertyValue(maxNumberOfResultExpression, "expression", String.class));
}
@Test
public void testUpdatingJpaOutboundGatewayParser() throws Exception {
setUp("JpaOutboundGatewayParserTests.xml", getClass(), "updatingJpaOutboundGateway");

View File

@@ -18,19 +18,18 @@
entity-manager-factory="entityManagerFactory"
auto-startup="true"
entity-class="org.springframework.integration.jpa.test.entity.StudentDomain"
expect-single-result="true"
order="1"
max-number-of-results="55"
request-channel="in"
reply-channel="out"
reply-timeout="100"
expect-single-result="true"
requires-reply="false"/>
<int-jpa:retrieving-outbound-gateway id="retrievingJpaOutboundGatewayWithFirstResult"
entity-manager-factory="entityManagerFactory"
auto-startup="true"
entity-class="org.springframework.integration.jpa.test.entity.StudentDomain"
expect-single-result="true"
order="1"
first-result="1"
max-number-of-results="55"
@@ -39,11 +38,22 @@
reply-timeout="100"
requires-reply="false"/>
<int-jpa:retrieving-outbound-gateway id="retrievingJpaOutboundGatewayWithMaxResultExpression"
entity-manager-factory="entityManagerFactory"
auto-startup="true"
entity-class="org.springframework.integration.jpa.test.entity.StudentDomain"
order="1"
first-result="1"
max-results-expression="header['maxResults']"
request-channel="in"
reply-channel="out"
reply-timeout="100"
requires-reply="false"/>
<int-jpa:retrieving-outbound-gateway id="retrievingJpaOutboundGatewayWithFirstResultExpression"
entity-manager-factory="entityManagerFactory"
auto-startup="true"
entity-class="org.springframework.integration.jpa.test.entity.StudentDomain"
expect-single-result="true"
order="1"
first-result-expression="header['firstResult']"
max-number-of-results="55"
@@ -52,20 +62,6 @@
reply-timeout="100"
requires-reply="false"/>
<int-jpa:retrieving-outbound-gateway id="retrievingJpaOutboundGatewayWithExpressionExecutor"
entity-manager-factory="entityManagerFactory"
auto-startup="true"
entity-class="org.springframework.integration.jpa.test.entity.StudentDomain"
expect-single-result="true"
order="1"
first-result-expression="header['firstResult']"
max-number-of-results="55"
request-channel="in"
reply-channel="out"
reply-timeout="100"
requires-reply="false"/>
<int-jpa:updating-outbound-gateway id="updatingJpaOutboundGateway"
entity-manager-factory="entityManagerFactory"
auto-startup="false"

View File

@@ -538,4 +538,11 @@ public class AbstractJpaOperationsTests {
List<?> results = jpaOperations.getResultListForQuery(query, null, 2, 0);
assertEquals(1, results.size());
}
public void testWithNegativeMaxNumberofResults() {
JpaOperations jpaOperations = getJpaOperations(entityManager);
String query = "select s from Student s";
List<?> results = jpaOperations.getResultListForQuery(query, null, 0, -1);
assertEquals(3, results.size());
}
}

View File

@@ -27,6 +27,7 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.integration.Message;
import org.springframework.integration.jpa.support.JpaParameter;
import org.springframework.integration.jpa.support.parametersource.ExpressionEvaluatingParameterSourceFactory;
@@ -52,6 +53,8 @@ public class JpaExecutorTests {
@Autowired
protected EntityManager entityManager;
private final StandardEvaluationContext ctx = new StandardEvaluationContext();
/**
* In this test, the {@link JpaExecutor}'s poll method will be called without
* specifying a 'query', 'namedQuery' or 'entityClass' property. This should
@@ -63,7 +66,7 @@ public class JpaExecutorTests {
public void testExecutePollWithNoEntityClassSpecified() throws Exception {
final JpaExecutor jpaExecutor = new JpaExecutor(mock(EntityManager.class));
jpaExecutor.afterPropertiesSet();
try {
jpaExecutor.poll();
} catch (IllegalStateException e) {
@@ -210,27 +213,12 @@ public class JpaExecutorTests {
return executor;
}
@Test
public void testNegativeMaxNumberOfResults() throws Exception {
final JpaExecutor jpaExecutor = new JpaExecutor(mock(EntityManager.class));
try {
jpaExecutor.setMaxNumberOfResults(-10);
} catch (IllegalArgumentException e) {
Assert.assertEquals("maxNumberOfResults must not be negative.", e.getMessage());
return;
}
Assert.fail("Was expecting an IllegalStateException to be thrown.");
}
@Test
public void testResultStartingFromThirdRecordForJPAQuery() throws Exception {
final JpaExecutor jpaExecutor = new JpaExecutor(entityManager);
jpaExecutor.setJpaQuery("select s from Student s");
jpaExecutor.setFirstResultExpression(new LiteralExpression("2"));
jpaExecutor.setIntegrationEvaluationContext(ctx);
jpaExecutor.afterPropertiesSet();
List<?> results = (List<?>)jpaExecutor.poll(MessageBuilder.withPayload("").build());
@@ -243,6 +231,7 @@ public class JpaExecutorTests {
final JpaExecutor jpaExecutor = new JpaExecutor(entityManager);
jpaExecutor.setNativeQuery("select * from Student s");
jpaExecutor.setFirstResultExpression(new LiteralExpression("2"));
jpaExecutor.setIntegrationEvaluationContext(ctx);
jpaExecutor.afterPropertiesSet();
List<?> results = (List<?>)jpaExecutor.poll(MessageBuilder.withPayload("").build());
Assert.assertNotNull(results);
@@ -254,6 +243,7 @@ public class JpaExecutorTests {
final JpaExecutor jpaExecutor = new JpaExecutor(entityManager);
jpaExecutor.setNamedQuery("selectAllStudents");
jpaExecutor.setFirstResultExpression(new LiteralExpression("2"));
jpaExecutor.setIntegrationEvaluationContext(ctx);
jpaExecutor.afterPropertiesSet();
List<?> results = (List<?>)jpaExecutor.poll(MessageBuilder.withPayload("").build());
Assert.assertNotNull(results);
@@ -265,9 +255,22 @@ public class JpaExecutorTests {
final JpaExecutor jpaExecutor = new JpaExecutor(entityManager);
jpaExecutor.setEntityClass(StudentDomain.class);
jpaExecutor.setFirstResultExpression(new LiteralExpression("2"));
jpaExecutor.setIntegrationEvaluationContext(ctx);
jpaExecutor.afterPropertiesSet();
List<?> results = (List<?>)jpaExecutor.poll(MessageBuilder.withPayload("").build());
Assert.assertNotNull(results);
Assert.assertEquals(1, results.size());
}
@Test
public void withNullMaxResultsExpression() {
final JpaExecutor jpaExecutor = new JpaExecutor(mock(EntityManager.class));
try {
jpaExecutor.setMaxResultsExpression(null);
} catch (Exception e) {
Assert.assertEquals("maxResultsExpression cannot be null", e.getMessage());
return;
}
Assert.fail("Expected the test case to throw an exception");
}
}

View File

@@ -29,9 +29,11 @@ import javax.persistence.EntityManager;
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.context.support.GenericApplicationContext;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
@@ -94,6 +96,7 @@ public class JpaPollingChannelAdapterTests {
final JpaExecutor jpaExecutor = new JpaExecutor(entityManager);
jpaExecutor.setEntityClass(StudentDomain.class);
jpaExecutor.afterPropertiesSet();
final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor);
@@ -137,6 +140,7 @@ public class JpaPollingChannelAdapterTests {
final JpaExecutor jpaExecutor = new JpaExecutor(entityManager);
jpaExecutor.setJpaQuery("from Student");
jpaExecutor.afterPropertiesSet();
final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor);
@@ -180,7 +184,8 @@ public class JpaPollingChannelAdapterTests {
final JpaExecutor jpaExecutor = new JpaExecutor(entityManager);
jpaExecutor.setJpaQuery("from Student");
jpaExecutor.setMaxNumberOfResults(1);
jpaExecutor.setMaxResultsExpression(new LiteralExpression("1"));
jpaExecutor.afterPropertiesSet();
final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor);
@@ -224,7 +229,7 @@ public class JpaPollingChannelAdapterTests {
final JpaExecutor jpaExecutor = new JpaExecutor(entityManager);
jpaExecutor.setJpaQuery("from Student s where s.firstName = 'First Two'");
jpaExecutor.afterPropertiesSet();
final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor);
final SourcePollingChannelAdapter adapter = JpaTestUtils.getSourcePollingChannelAdapter(
@@ -273,6 +278,7 @@ public class JpaPollingChannelAdapterTests {
jpaExecutor.setJpaQuery("from Student s");
jpaExecutor.setDeleteAfterPoll(true);
jpaExecutor.setDeleteInBatch(true);
jpaExecutor.afterPropertiesSet();
final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor);
@@ -332,6 +338,7 @@ public class JpaPollingChannelAdapterTests {
final JpaExecutor jpaExecutor = new JpaExecutor(entityManager);
jpaExecutor.setJpaQuery("from Student s where s.lastName = 'Something Else'");
jpaExecutor.setDeleteAfterPoll(true);
jpaExecutor.afterPropertiesSet();
final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor);
@@ -371,6 +378,7 @@ public class JpaPollingChannelAdapterTests {
jpaExecutor.setJpaQuery("from Student s");
jpaExecutor.setDeleteAfterPoll(true);
jpaExecutor.setDeleteInBatch(false);
jpaExecutor.afterPropertiesSet();
final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor);
@@ -417,6 +425,7 @@ public class JpaPollingChannelAdapterTests {
final JpaExecutor jpaExecutor = new JpaExecutor(entityManager);
jpaExecutor.setNativeQuery("select * from Student where lastName = 'Last One'");
jpaExecutor.afterPropertiesSet();
final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor);
@@ -460,6 +469,7 @@ public class JpaPollingChannelAdapterTests {
final JpaExecutor jpaExecutor = new JpaExecutor(entityManager);
jpaExecutor.setNamedQuery("selectStudent");
jpaExecutor.afterPropertiesSet();
final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor);

View File

@@ -21,5 +21,6 @@
request-channel="in"
reply-channel="out"
first-result-expression="payload"
max-results-expression="headers['maxResults']"
reply-timeout="100"/>
</beans>

View File

@@ -54,11 +54,11 @@ public class JpaOutboundGatewayIntegrationTests {
/**
* Sends a message with the payload as a integer representing the start number in the result
* set.
* set and a header with value maxResults to get the max number of results
* @throws Exception
*/
@Test
public void retrieveFromSecondRecord() throws Exception {
public void retrieveFromSecondRecordAndMaximumOneRecord() throws Exception {
responseChannel.subscribe(new MessageHandler() {
@SuppressWarnings("rawtypes")
@Override
@@ -66,7 +66,10 @@ public class JpaOutboundGatewayIntegrationTests {
assertEquals(1, ((List)message.getPayload()).size());
}
});
Message<Integer> message = MessageBuilder.withPayload(2).build();
Message<Integer> message = MessageBuilder
.withPayload(1)
.setHeader("maxResults", "1")
.build();
requestChannel.send(message);
}
}

View File

@@ -31,6 +31,7 @@
<int:method name="getStudent2" request-channel="retrievingGatewayInsideChain" />
<int:method name="persistStudent2" request-channel="updatingGatewayInsideChain" />
<int:method name="getAllStudentsFromGivenRecord" request-channel="getStudentsFromGivenRecordChannel"/>
<int:method name="getStudents" request-channel="getStudentsWithMaxNumberOfRecordsChannel"/>
</int:gateway>
<int:channel id="studentReplyChannel"/>
@@ -45,6 +46,7 @@
<int:channel id="retrievingGatewayInsideChain"/>
<int:channel id="updatingGatewayInsideChain"/>
<int:channel id="getStudentsFromGivenRecordChannel"/>
<int:channel id="getStudentsWithMaxNumberOfRecordsChannel"/>
<bean id="deleteStudentEndpoint"
class="org.springframework.integration.endpoint.EventDrivenConsumer">
@@ -220,6 +222,29 @@
</constructor-arg>
</bean>
<bean id="getStudentsWithMaxNumberOfRecords"
class="org.springframework.integration.endpoint.EventDrivenConsumer">
<constructor-arg name="inputChannel" ref="getStudentsWithMaxNumberOfRecordsChannel"/>
<constructor-arg name="handler">
<bean class="org.springframework.integration.jpa.outbound.JpaOutboundGateway">
<constructor-arg name="jpaExecutor">
<bean class="org.springframework.integration.jpa.core.JpaExecutor">
<constructor-arg name="entityManager" ref="entityManager"/>
<property name="jpaQuery" value="select s from Student s"/>
<property name="expectSingleResult" value="false"/>
<property name="firstResultExpression"
value="#{new org.springframework.expression.spel.standard.SpelExpressionParser().parseExpression('payload')}"/>
<property name="maxResultsExpression"
value="#{new org.springframework.expression.spel.standard.SpelExpressionParser().parseExpression('payload')}"/>
</bean>
</constructor-arg>
<property name="gatewayType" value="RETRIEVING"/>
<property name="outputChannel" ref="studentReplyChannel"/>
</bean>
</constructor-arg>
</bean>
<int:chain input-channel="retrievingGatewayInsideChain" output-channel="studentReplyChannel">
<jpa:retrieving-outbound-gateway entity-manager="entityManager"
expect-single-result="true"

View File

@@ -67,6 +67,14 @@ public class JpaOutboundGatewayTests {
Assert.assertEquals(2, students.size());
}
@Test
public void getAllStudentsWithMaxNumberOfRecords() {
List<?> students = studentService.getStudents(1);
Assert.assertNotNull(students);
Assert.assertEquals(1, students.size());
}
@Test
public void deleteNonExistingStudent() {

View File

@@ -40,4 +40,6 @@ public interface StudentService {
StudentDomain getStudent2(Long id);
StudentDomain persistStudent2(StudentDomain studentToPersist);
List<StudentDomain> getStudents(int maxNumberOfRecords);
}

View File

@@ -408,6 +408,8 @@
auto-startup="true" ]]><co id="inboundAdapterAutoStartup"/><![CDATA[
query="select s from Student s" ]]><co id="inboundAdapterQuery"/><![CDATA[
expect-single-result="true" ]]><co id="inboundAdapterExpectResult"/><![CDATA[
max-number-of-results="" ]]><co id="inboundAdapterMaxResults"/><![CDATA[
max-results-expression="" ]]><co id="inboundAdapterMaxResultsExpression"/><![CDATA[
delete-after-poll="true"> ]]><co id="inboundAdapterDeleteAfterPoll"/><![CDATA[
<int:poller fixed-rate="2000" >
<int:transactional propagation="REQUIRED" transaction-manager="transactionManager"/>
@@ -449,6 +451,21 @@
<classname>MessagingException</classname> is thrown. The value defaults to <code>false</code>.
</para>
</callout>
<callout arearefs="inboundAdapterMaxResults">
<para>
This non zero, non negative integer value tells the adapter not to select more than given number
of rows on execution of the select operation. By default, if this attribute is not set,
all the possible records are selected by given query. This attribute is mutually exclusive to
<code>max-results-expression</code>. <emphasis>Optional</emphasis>.
</para>
</callout>
<callout arearefs="inboundAdapterMaxResultsExpression">
<para>
An expression mutually exclusive to <code>max-number-of-results</code> that can
be used to provide an expression that will be evaluated to find the maximum number of results
in a result set. <emphasis>Optional</emphasis>.
</para>
</callout>
<callout arearefs="inboundAdapterDeleteAfterPoll">
<para>
Set this value to <code>true</code> if you want
@@ -1138,6 +1155,7 @@ public class Student {
jpa-operations=""
jpa-query=""
max-number-of-results="" ]]><co id="outGateMaxNumOfResults"/><![CDATA[
max-results-expression="" ]]><co id="outGateMaxNumOfResultsExpression"/><![CDATA[
first-result="" ]]><co id="outGateFirstResult"/><![CDATA[
first-result-expression="" ]]><co id="outGateFirstResultExpression"/><![CDATA[
named-query=""
@@ -1167,7 +1185,15 @@ public class Student {
<para>
This non zero, non negative integer value tells the adapter not to select more than given number
of rows on execution of the select operation. By default, if this attribute is not set,
all the possible records are selected by given query.<emphasis>Optional</emphasis>.
all the possible records are selected by given query. This attribute is mutually exclusive to
<code>max-results-expression</code>. <emphasis>Optional</emphasis>.
</para>
</callout>
<callout arearefs="outGateMaxNumOfResultsExpression">
<para>
An expression mutually exclusive to <code>max-number-of-results</code> that can
be used to provide an expression that will be evaluated to find the maximum number of results
in a result set. <emphasis>Optional</emphasis>.
</para>
</callout>
<callout arearefs="outGateFirstResult">
@@ -1319,4 +1345,4 @@ public class Student {
</note>
</section>
</section>
</chapter>
</chapter>

View File

@@ -471,7 +471,17 @@
Retrieving gateways had no mechanism to specify the first record to be retrieved which
is a common use case. The retrieving gateways now support specifying this parameter
using a <code>first-result</code> and <code>first-result-expression</code> attributes
to the gateway definition.<xref linkend="jpa-retrieving-outbound-gateway"/>
to the gateway definition. <xref linkend="jpa-retrieving-outbound-gateway"/>.
</para>
</section>
<section id="3.0-jpa-max-results">
<title>JPA Adapters: max-results-expression attribute</title>
<para>
Retrieving gateways and inbound adapters now has a flexibility to specify the maximum
number of results in a result set as an expression. We now have <code>max-number-of-results</code>
and <code>max-results-expression</code> attributes which are used to provide the
maximum number of results and the expression to compute the maximum number of results in the
result set respectively. For more information see <xref linkend="jpa"/>.
</para>
</section>
</section>