INT-2881: Add support for the firstRecord to retrieving JPA gateway

JIRA: https://jira.springsource.org/browse/INT-2881
This commit is contained in:
Amol Nayak
2013-09-27 19:43:55 +03:00
committed by Artem Bilan
parent df6b821e4b
commit 61f24c8d9e
21 changed files with 516 additions and 84 deletions

View File

@@ -470,5 +470,4 @@ public abstract class IntegrationNamespaceUtils {
.addConstructorArgValue(methodSignature);
registry.registerBeanDefinition(functionId, builder.getBeanDefinition());
}
}

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,13 +15,17 @@
*/
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;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.jpa.support.OutboundGatewayType;
import org.w3c.dom.Element;
/**
* The Parser for the Retrieving Jpa Outbound Gateway.
@@ -41,6 +45,13 @@ public class RetrievingJpaOutboundGatewayParser extends AbstractJpaOutboundGatew
final BeanDefinitionBuilder jpaExecutorBuilder = JpaParserUtils.getOutboundGatewayJpaExecutorBuilder(gatewayElement, parserContext);
RootBeanDefinition firstResultExpression = createExpressionDefinitionFromValueOrExpression("first-result",
"first-result-expression", parserContext, gatewayElement, false);
if(firstResultExpression != null) {
jpaExecutorBuilder.addPropertyValue("firstResultExpression", firstResultExpression);
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, gatewayElement, "max-number-of-results");
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, gatewayElement, "delete-after-poll");
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, gatewayElement, "delete-in-batch");
@@ -55,7 +66,5 @@ public class RetrievingJpaOutboundGatewayParser extends AbstractJpaOutboundGatew
jpaOutboundGatewayBuilder.addConstructorArgReference(jpaExecutorBeanName);
jpaOutboundGatewayBuilder.addPropertyValue("gatewayType", OutboundGatewayType.RETRIEVING);
return jpaOutboundGatewayBuilder;
}
}

View File

@@ -25,6 +25,7 @@ import javax.persistence.Query;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.integration.jpa.support.JpaUtils;
import org.springframework.integration.jpa.support.parametersource.ParameterSource;
import org.springframework.integration.jpa.support.parametersource.PositionSupportingParameterSource;
@@ -45,11 +46,13 @@ public class DefaultJpaOperations extends AbstractJpaOperations {
private static final Log logger = LogFactory.getLog(DefaultJpaOperations.class);
@Override
public void delete(Object entity) {
Assert.notNull(entity, "The entity must not be null!");
entityManager.remove(entity);
}
@Override
public void deleteInBatch(Iterable<?> entities) {
Assert.notNull(entities, "entities must not be null.");
@@ -80,24 +83,28 @@ public class DefaultJpaOperations extends AbstractJpaOperations {
}
@Override
public int executeUpdate(String updateQuery, ParameterSource source) {
Query query = entityManager.createQuery(updateQuery);
setParametersIfRequired(updateQuery, source, query);
return query.executeUpdate();
}
@Override
public int executeUpdateWithNamedQuery(String updateQuery, ParameterSource source) {
Query query = entityManager.createNamedQuery(updateQuery);
setParametersIfRequired(updateQuery, source, query);
return query.executeUpdate();
}
@Override
public int executeUpdateWithNativeQuery(String updateQuery, ParameterSource source) {
Query query = entityManager.createNativeQuery(updateQuery);
setParametersIfRequired(updateQuery, source, query);
return query.executeUpdate();
}
@Override
public <T> T find(Class<T> entityType, Object id) {
return entityManager.find(entityType, id);
}
@@ -108,11 +115,15 @@ public class DefaultJpaOperations extends AbstractJpaOperations {
return query;
}
public List<?> getResultListForClass(Class<?> entityClass, int maxNumberOfResults) {
@Override
public List<?> getResultListForClass(Class<?> entityClass, int firstResult, int maxNumberOfResults) {
final String entityName = JpaUtils.getEntityName(entityManager, entityClass);
final Query query = entityManager.createQuery("select x from " + entityName + " x", entityClass);
if(firstResult > 0) {
query.setFirstResult(firstResult);
}
if(maxNumberOfResults > 0) {
query.setMaxResults(maxNumberOfResults);
}
@@ -121,12 +132,16 @@ public class DefaultJpaOperations extends AbstractJpaOperations {
}
@Override
public List<?> getResultListForNamedQuery(String selectNamedQuery,
ParameterSource parameterSource, int maxNumberOfResults) {
ParameterSource parameterSource, int firstResult, int maxNumberOfResults) {
final Query query = entityManager.createNamedQuery(selectNamedQuery);
setParametersIfRequired(selectNamedQuery, parameterSource, query);
if(firstResult > 0) {
query.setFirstResult(firstResult);
}
if(maxNumberOfResults > 0) {
query.setMaxResults(maxNumberOfResults);
}
@@ -135,8 +150,9 @@ public class DefaultJpaOperations extends AbstractJpaOperations {
}
@Override
public List<?> getResultListForNativeQuery(String selectQuery, Class<?> entityClass,
ParameterSource parameterSource, int maxNumberOfResults) {
ParameterSource parameterSource, int firstResult, int maxNumberOfResults) {
final Query query;
@@ -148,6 +164,9 @@ public class DefaultJpaOperations extends AbstractJpaOperations {
setParametersIfRequired(selectQuery, parameterSource, query);
if(firstResult > 0) {
query.setFirstResult(firstResult);
}
if(maxNumberOfResults > 0) {
query.setMaxResults(maxNumberOfResults);
}
@@ -155,15 +174,20 @@ public class DefaultJpaOperations extends AbstractJpaOperations {
return query.getResultList();
}
@Override
public List<?> getResultListForQuery(String query, ParameterSource source) {
return getResultListForQuery(query,source, 0);
return getResultListForQuery(query,source, 0, 0);
}
@Override
public List<?> getResultListForQuery(String queryString, ParameterSource source,
int maxNumberOfResults) {
int firstResult, int maxNumberOfResults) {
Query query = getQuery(queryString,source);
if(firstResult > 0) {
query.setFirstResult(firstResult);
}
if(maxNumberOfResults > 0) {
query.setMaxResults(maxNumberOfResults);
}
@@ -171,16 +195,19 @@ public class DefaultJpaOperations extends AbstractJpaOperations {
return query.getResultList();
}
@Override
public Object getSingleResultForQuery(String queryString, ParameterSource source) {
Query query = getQuery(queryString,source);
return query.getSingleResult();
}
@Override
public Object merge(Object entity) {
Assert.notNull(entity, "The object to merge must not be null.");
return persistOrMerge(entity, true);
}
@Override
public void persist(Object entity) {
Assert.notNull(entity, "The object to persist must not be null.");
persistOrMerge(entity, false);

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.
@@ -25,8 +25,11 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
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.integration.Message;
import org.springframework.integration.MessagingException;
import org.springframework.integration.expression.IntegrationEvaluationContextAware;
import org.springframework.integration.jpa.support.JpaParameter;
import org.springframework.integration.jpa.support.PersistMode;
import org.springframework.integration.jpa.support.parametersource.BeanPropertyParameterSourceFactory;
@@ -60,7 +63,7 @@ import org.springframework.util.Assert;
* @since 2.2
*
*/
public class JpaExecutor implements InitializingBean, BeanFactoryAware {
public class JpaExecutor implements InitializingBean, BeanFactoryAware, IntegrationEvaluationContextAware {
private volatile JpaOperations jpaOperations;
private volatile List<JpaParameter> jpaParameters;
@@ -73,6 +76,8 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware {
/** 0 means all possible objects shall be retrieved. */
private volatile int maxNumberOfResults = 0;
private volatile Expression firstResultExpression;
private volatile PersistMode persistMode = PersistMode.MERGE;
private volatile ParameterSourceFactory parameterSourceFactory = null;
@@ -93,6 +98,8 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware {
private volatile BeanFactory beanFactory;
private volatile EvaluationContext evaluationContext;
/**
* Constructor taking an {@link EntityManagerFactory} from which the
* {@link EntityManager} can be obtained.
@@ -148,6 +155,7 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware {
* {@link ParameterSourceFactory}.
*
*/
@Override
public void afterPropertiesSet() {
if (this.jpaParameters != null) {
@@ -247,6 +255,7 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware {
}
/**
* Execute a (typically retrieving) JPA operation. The <i>requestMessage</i>
* can be used to provide additional query parameters using
@@ -259,18 +268,18 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware {
*/
@SuppressWarnings("unchecked")
public Object poll(final Message<?> requestMessage) {
final Object payload;
final List<?> result;
if (requestMessage == null) {
result = doPoll(this.parameterSource);
result = doPoll(this.parameterSource, 0);
}
else {
int firstResult = 0;
if(firstResultExpression != null) {
firstResult = getFirstResult(requestMessage);
}
ParameterSource parameterSource = determineParameterSource(requestMessage);
result = doPoll(parameterSource);
result = doPoll(parameterSource, firstResult);
}
if (result.isEmpty()) {
@@ -283,21 +292,17 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware {
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;
}
}
if (payload != null && this.deleteAfterPoll) {
if (payload instanceof Iterable) {
if (this.deleteInBatch) {
this.jpaOperations.deleteInBatch((Iterable<Object>) payload);
@@ -313,10 +318,34 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware {
}
}
return payload;
}
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);
}
catch (NumberFormatException e) {
throw new IllegalArgumentException(
"Value " + firstRecordEvaluationResult + " passed as firstRecord cannot be " +
"parsed to a number");
}
}
else {
throw new IllegalArgumentException("Expected the value of the firstRecord to be a Number" +
" got " + firstRecordEvaluationResult.getClass().getName());
}
}
return firstResult;
}
private ParameterSource determineParameterSource(final Message<?> requestMessage) {
ParameterSource parameterSource;
if (usePayloadAsParameterSource) {
@@ -335,28 +364,29 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware {
return poll(null);
}
protected List<?> doPoll(ParameterSource jpaQLParameterSource) {
protected List<?> doPoll(ParameterSource jpaQLParameterSource, int firstResult) {
List<?> payload = null;
if (this.jpaQuery != null) {
payload = jpaOperations.getResultListForQuery(this.jpaQuery, jpaQLParameterSource, maxNumberOfResults);
payload = jpaOperations.getResultListForQuery(this.jpaQuery, jpaQLParameterSource,
firstResult, maxNumberOfResults);
}
else if (this.nativeQuery != null) {
payload = jpaOperations.getResultListForNativeQuery(this.nativeQuery, this.entityClass, jpaQLParameterSource, maxNumberOfResults);
payload = jpaOperations.getResultListForNativeQuery(this.nativeQuery, this.entityClass, jpaQLParameterSource,
firstResult, maxNumberOfResults);
}
else if (this.namedQuery != null) {
payload = jpaOperations.getResultListForNamedQuery(this.namedQuery, jpaQLParameterSource, maxNumberOfResults);
payload = jpaOperations.getResultListForNamedQuery(this.namedQuery, jpaQLParameterSource,
firstResult, maxNumberOfResults);
}
else if (this.entityClass != null) {
payload = jpaOperations.getResultListForClass(this.entityClass, maxNumberOfResults);
payload = jpaOperations.getResultListForClass(this.entityClass,
firstResult, maxNumberOfResults);
}
else {
throw new IllegalStateException("For the polling operation, one of "
+ "the following properties must be specified: "
+ "query, namedQuery or entityClass.");
}
return payload;
}
@@ -509,4 +539,28 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware {
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
*
* @param firstResultExpression
*
* @see Query#setFirstResult(int)
*/
public void setFirstResultExpression(Expression firstResultExpression) {
this.firstResultExpression = firstResultExpression;
}
/**
* Sets the evaluation context for evaluating the expression to get the from record of the
* result set retrieved by the retrieving gateway.
*
* @param evaluationContext
*/
@Override
public void setIntegrationEvaluationContext(
EvaluationContext evaluationContext) {
this.evaluationContext = evaluationContext;
}
}

View File

@@ -85,20 +85,24 @@ public interface JpaOperations {
/**
*
* @param entityClass
* @param firstResult
* @param maxNumberOfReturnedObjects
* @return List of found entities
*/
List<?> getResultListForClass(Class<?> entityClass,
int firstResult,
int maxNumberOfReturnedObjects);
/**
*
* @param selectNamedQuery
* @param jpaQLParameterSource
* @param firstResult
* @param maxNumberOfResults
* @return List of found entities
*/
List<?> getResultListForNamedQuery(String selectNamedQuery, ParameterSource jpaQLParameterSource,
int firstResult,
int maxNumberOfResults);
/**
@@ -106,11 +110,14 @@ public interface JpaOperations {
* @param selectQuery
* @param entityClass
* @param jpaQLParameterSource
* @param firstResult
* @param maxNumberOfResults
* @return List of found entities
*/
List<?> getResultListForNativeQuery(String selectQuery,
Class<?> entityClass, ParameterSource jpaQLParameterSource, int maxNumberOfResults);
Class<?> entityClass, ParameterSource jpaQLParameterSource,
int firstResult,
int maxNumberOfResults);
/**
* Executes the provided query to return a list of results
@@ -124,11 +131,12 @@ public interface JpaOperations {
* Executes the provided query to return a list of results.
*
* @param query Must not be null or empty
* @param firstResult The first result
* @param maxNumberOfResults Must be a non-negative value
* @param source the Parameter source for this query to be executed, if none then set null
* @return List of found entities
*/
List<?> getResultListForQuery(String query, ParameterSource source, int maxNumberOfResults);
List<?> getResultListForQuery(String query, ParameterSource source, int firstResult, int maxNumberOfResults);
/**
* Executes the provided query to return a single element

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.
@@ -39,6 +39,8 @@ import org.springframework.util.Assert;
* constructor.
*
* @author Gunnar Hillert
* @author Amol Nayak
*
* @since 2.2
*
*/
@@ -57,6 +59,7 @@ public class JpaOutboundGateway extends AbstractReplyProducingMessageHandler {
public JpaOutboundGateway(JpaExecutor jpaExecutor) {
Assert.notNull(jpaExecutor, "jpaExecutor must not be null.");
this.jpaExecutor = jpaExecutor;
}
/**
@@ -70,21 +73,13 @@ public class JpaOutboundGateway extends AbstractReplyProducingMessageHandler {
@Override
protected Object handleRequestMessage(Message<?> requestMessage) {
final Object result;
if (OutboundGatewayType.RETRIEVING.equals(this.gatewayType)) {
result = this.jpaExecutor.poll(requestMessage);
} else if (OutboundGatewayType.UPDATING.equals(this.gatewayType)) {
result = this.jpaExecutor.executeOutboundJpaOperation(requestMessage);
} else {
throw new IllegalArgumentException(String.format("GatewayType '%s' is not supported.", this.gatewayType));
}
if (result == null || !producesReply) {
@@ -114,5 +109,4 @@ public class JpaOutboundGateway extends AbstractReplyProducingMessageHandler {
public void setProducesReply(boolean producesReply) {
this.producesReply = producesReply;
}
}

View File

@@ -155,7 +155,6 @@ public class JpaOutboundGatewayFactoryBean extends AbstractFactoryBean<MessageHa
}
jpaOutboundGateway.setBeanFactory(this.getBeanFactory());
jpaOutboundGateway.afterPropertiesSet();
if (!CollectionUtils.isEmpty(this.txAdviceChain)) {
ProxyFactory proxyFactory = new ProxyFactory(jpaOutboundGateway);
@@ -170,5 +169,4 @@ public class JpaOutboundGatewayFactoryBean extends AbstractFactoryBean<MessageHa
return jpaOutboundGateway;
}
}

View File

@@ -222,6 +222,29 @@
</xsd:element>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
<xsd:attribute name="first-result" type="xsd:integer">
<xsd:annotation>
<xsd:documentation>
The attribute that is used to set the first result marker while executing the
results. A negative value will retrieve from first record in the result set.
It is a way for the application to use the gateway to paginate the results in
combination with the max-number-of-results attribute. This attribute is mutually
exclusive to first-result-expression attribute
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="first-result-expression">
<xsd:annotation>
<xsd:documentation>
The attribute that is used to set the first result expression that would
be evaluated to get the first record while executing the JPA query for result
A negative value will retrieve from first record in the result set.
It is a way for the application to use the gateway to paginate the results in
combination with the max-number-of-results attribute. This attribute is mutually
exclusive to first-result attribute
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attributeGroup ref="coreJpaComponentAttributes" />
<xsd:attributeGroup ref="commonJpaOutboundGatewayAttributes"/>
<xsd:attributeGroup ref="commonRetrievingJpaAttributes" />

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-jpa="http://www.springframework.org/schema/integration/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/jpa http://www.springframework.org/schema/integration/jpa/spring-integration-jpa.xsd">
<import resource="classpath:/hibernateJpa-context.xml" />
<int:channel id="in"/>
<int:channel id="out"/>
<int-jpa:retrieving-outbound-gateway id="invalidRetrievingJpaOutboundGateway"
entity-manager-factory="entityManagerFactory"
auto-startup="true"
entity-class="org.springframework.integration.jpa.test.entity.StudentDomain"
order="1"
max-number-of-results="55"
first-result="1"
first-result-expression="header['firstResult']"
request-channel="in"
reply-channel="out"
reply-timeout="100"
requires-reply="false"/>
</beans>

View File

@@ -18,13 +18,17 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.After;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.integration.Message;
import org.springframework.integration.MessagingException;
import org.springframework.integration.channel.AbstractMessageChannel;
@@ -57,43 +61,47 @@ public class JpaOutboundGatewayParserTests extends AbstractRequestHandlerAdvice
@Test
public void testRetrievingJpaOutboundGatewayParser() throws Exception {
setUp("JpaOutboundGatewayParserTests.xml", getClass(), "retrievingJpaOutboundGateway");
final AbstractMessageChannel inputChannel = TestUtils.getPropertyValue(this.consumer, "inputChannel", AbstractMessageChannel.class);
assertEquals("in", inputChannel.getComponentName());
final JpaOutboundGateway jpaOutboundGateway = TestUtils.getPropertyValue(this.consumer, "handler", JpaOutboundGateway.class);
final OutboundGatewayType gatewayType = TestUtils.getPropertyValue(jpaOutboundGateway, "gatewayType", OutboundGatewayType.class);
assertEquals(OutboundGatewayType.RETRIEVING, gatewayType);
long sendTimeout = TestUtils.getPropertyValue(jpaOutboundGateway, "messagingTemplate.sendTimeout", Long.class);
assertEquals(100, sendTimeout);
assertFalse(TestUtils.getPropertyValue(jpaOutboundGateway, "requiresReply", Boolean.class));
final JpaExecutor jpaExecutor = TestUtils.getPropertyValue(this.consumer, "handler.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);
assertTrue(TestUtils.getPropertyValue(jpaExecutor, "expectSingleResult", Boolean.class));
final Integer maxNumberOfResults = TestUtils.getPropertyValue(jpaExecutor, "maxNumberOfResults", Integer.class);
assertEquals(Integer.valueOf(55), maxNumberOfResults);
}
@Test
public void testRetrievingJpaOutboundGatewayParserWithFirstResult() throws Exception {
setUp("JpaOutboundGatewayParserTests.xml", getClass(), "retrievingJpaOutboundGatewayWithFirstResult");
final JpaOutboundGateway jpaOutboundGateway = TestUtils.getPropertyValue(this.consumer, "handler", JpaOutboundGateway.class);
Expression firstResultExpression =
TestUtils.getPropertyValue(jpaOutboundGateway, "jpaExecutor.firstResultExpression", Expression.class);
assertNotNull(firstResultExpression);
assertEquals(LiteralExpression.class, firstResultExpression.getClass());
assertEquals("1", TestUtils.getPropertyValue(firstResultExpression, "literalValue", String.class));
}
@Test
public void testRetrievingJpaOutboundGatewayParserWithFirstResultExpression() throws Exception {
setUp("JpaOutboundGatewayParserTests.xml", getClass(), "retrievingJpaOutboundGatewayWithFirstResultExpression");
final JpaOutboundGateway jpaOutboundGateway = TestUtils.getPropertyValue(this.consumer, "handler", JpaOutboundGateway.class);
Expression firstResultExpression =
TestUtils.getPropertyValue(jpaOutboundGateway, "jpaExecutor.firstResultExpression", Expression.class);
assertNotNull(firstResultExpression);
assertEquals(SpelExpression.class, firstResultExpression.getClass());
assertEquals("header['firstResult']", TestUtils.getPropertyValue(firstResultExpression, "expression", String.class));
}
@Test
public void testUpdatingJpaOutboundGatewayParser() throws Exception {
setUp("JpaOutboundGatewayParserTests.xml", getClass(), "updatingJpaOutboundGateway");
@@ -170,6 +178,18 @@ public class JpaOutboundGatewayParserTests extends AbstractRequestHandlerAdvice
}
@Test
public void withBothFirstResultAndFirstResultExpressionPresent() {
try {
this.context = new ClassPathXmlApplicationContext("JpaInvalidOutboundGatewayParserTests.xml", getClass());
} catch (BeanDefinitionStoreException e) {
assertTrue(e.getMessage().startsWith("Configuration problem: Only one of 'first-result' or 'first-result-expression' is allowed"));
return;
}
fail("BeanDefinitionStoreException expected.");
}
@After
public void tearDown() {
if (context != null) {

View File

@@ -26,6 +26,46 @@
reply-timeout="100"
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"
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"
request-channel="in"
reply-channel="out"
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"
@@ -59,5 +99,4 @@
<bean class="org.springframework.integration.jpa.config.xml.JpaOutboundGatewayParserTests$FooAdvice"/>
</constructor-arg>
</bean>
</beans>

View File

@@ -24,6 +24,7 @@ import java.util.List;
import javax.persistence.EntityManager;
import org.junit.Assert;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.jpa.support.parametersource.ExpressionEvaluatingParameterSourceFactory;
@@ -60,7 +61,7 @@ public class AbstractJpaOperationsTests {
final JpaOperations jpaOperations = getJpaOperations(entityManager);
final List<?> students = jpaOperations.getResultListForClass(StudentDomain.class, 0);
final List<?> students = jpaOperations.getResultListForClass(StudentDomain.class, 0, 0);
Assert.assertTrue(students.size() == 3);
}
@@ -72,7 +73,7 @@ public class AbstractJpaOperationsTests {
final JpaOperations jpaOperations = getJpaOperations(entityManager);
final List<?> students = jpaOperations.getResultListForClass(StudentDomain.class, 2);
final List<?> students = jpaOperations.getResultListForClass(StudentDomain.class, 0, 2);
Assert.assertTrue(String.format("Was expecting 2 Students to be returned but got '%s'.", students.size()),
students.size() == 2);
@@ -87,7 +88,7 @@ public class AbstractJpaOperationsTests {
final StudentDomain student = JpaTestUtils.getTestStudent();
List<?> students = jpaOperations.getResultListForClass(StudentDomain.class, 0);
List<?> students = jpaOperations.getResultListForClass(StudentDomain.class, 0, 0);
Assert.assertTrue(students.size() == 3);
ParameterSourceFactory requestParameterSourceFactory =
@@ -156,7 +157,7 @@ public class AbstractJpaOperationsTests {
Class<?> entityClass = StudentDomain.class;
List<?> students = jpaOperations.getResultListForNativeQuery(selectSqlQuery, entityClass, null, 0);
List<?> students = jpaOperations.getResultListForNativeQuery(selectSqlQuery, entityClass, null, 0, 0);
Assert.assertTrue(students.size() == 1);
@@ -181,7 +182,7 @@ public class AbstractJpaOperationsTests {
String selectSqlQuery = "select rollNumber, firstName, lastName, gender, dateOfBirth, lastUpdated from Student where lastName = 'Last One'";
List<?> students = jpaOperations.getResultListForNativeQuery(selectSqlQuery, null, null, 0);
List<?> students = jpaOperations.getResultListForNativeQuery(selectSqlQuery, null, null, 0, 0);
Assert.assertTrue(students.size() == 1);
@@ -285,7 +286,7 @@ public class AbstractJpaOperationsTests {
public void testMergeCollectionWithNullElement() {
final JpaOperations jpaOperations = getJpaOperations(entityManager);
final List<?> studentsFromDbBeforeTest = jpaOperations.getResultListForClass(StudentDomain.class, 0);
final List<?> studentsFromDbBeforeTest = jpaOperations.getResultListForClass(StudentDomain.class, 0, 0);
Assert.assertEquals(3, studentsFromDbBeforeTest.size());
@@ -379,7 +380,7 @@ public class AbstractJpaOperationsTests {
public void testPersistCollectionWithNullElement() {
final JpaOperations jpaOperations = getJpaOperations(entityManager);
final List<?> studentsFromDbBeforeTest = jpaOperations.getResultListForClass(StudentDomain.class, 0);
final List<?> studentsFromDbBeforeTest = jpaOperations.getResultListForClass(StudentDomain.class, 0, 0);
Assert.assertEquals(3, studentsFromDbBeforeTest.size());
@@ -405,7 +406,7 @@ public class AbstractJpaOperationsTests {
Assert.assertNotNull(student1.getRollNumber());
Assert.assertNotNull(student3.getRollNumber());
final List<?> studentsFromDb = jpaOperations.getResultListForClass(StudentDomain.class, 0);
final List<?> studentsFromDb = jpaOperations.getResultListForClass(StudentDomain.class, 0, 0);
Assert.assertNotNull(studentsFromDb);
Assert.assertEquals(5, studentsFromDb.size());
@@ -414,7 +415,7 @@ public class AbstractJpaOperationsTests {
public void testDeleteInBatch() {
final JpaOperations jpaOperations = getJpaOperations(entityManager);
final List<?> students = jpaOperations.getResultListForClass(StudentDomain.class, 0);
final List<?> students = jpaOperations.getResultListForClass(StudentDomain.class, 0, 0);
Assert.assertNotNull(students);
@@ -432,7 +433,7 @@ public class AbstractJpaOperationsTests {
transactionManager.commit(status);
final List<?> studentsFromDb = jpaOperations.getResultListForClass(StudentDomain.class, 0);
final List<?> studentsFromDb = jpaOperations.getResultListForClass(StudentDomain.class, 0, 0);
Assert.assertNotNull(studentsFromDb);
Assert.assertTrue(studentsFromDb.size() == 0);
@@ -451,7 +452,7 @@ public class AbstractJpaOperationsTests {
public void testDelete() {
final JpaOperations jpaOperations = getJpaOperations(entityManager);
final List<?> studentsFromDb = jpaOperations.getResultListForClass(StudentDomain.class, 0);
final List<?> studentsFromDb = jpaOperations.getResultListForClass(StudentDomain.class, 0, 0);
Assert.assertNotNull(studentsFromDb);
Assert.assertTrue(studentsFromDb.size() == 3);
@@ -475,7 +476,7 @@ public class AbstractJpaOperationsTests {
transactionManager.commit(status);
final List<?> studentsFromDbAfterDelete = jpaOperations.getResultListForClass(StudentDomain.class, 0);
final List<?> studentsFromDbAfterDelete = jpaOperations.getResultListForClass(StudentDomain.class, 0, 0);
Assert.assertNotNull(studentsFromDbAfterDelete);
Assert.assertTrue(studentsFromDbAfterDelete.size() == 2);
@@ -485,7 +486,7 @@ public class AbstractJpaOperationsTests {
public void testDeleteInBatchWithEmptyCollection() {
final JpaOperations jpaOperations = getJpaOperations(entityManager);
final List<?> students = jpaOperations.getResultListForClass(StudentDomain.class, 0);
final List<?> students = jpaOperations.getResultListForClass(StudentDomain.class, 0, 0);
Assert.assertNotNull(students);
Assert.assertTrue(students.size() == 3);
@@ -503,11 +504,38 @@ public class AbstractJpaOperationsTests {
transactionManager.commit(status);
final List<?> studentsFromDb = jpaOperations.getResultListForClass(StudentDomain.class, 0);
final List<?> studentsFromDb = jpaOperations.getResultListForClass(StudentDomain.class, 0, 0);
Assert.assertNotNull(studentsFromDb);
Assert.assertTrue(studentsFromDb.size() == 3); //Nothing should have happened
}
public void testGetAllStudentsFromThirdRecord() {
JpaOperations jpaOperations = getJpaOperations(entityManager);
List<?> results = jpaOperations.getResultListForClass(StudentDomain.class, 2, 0);
assertEquals(1, results.size());
}
public void testGetAllStudentsUsingNativeQueryFromThirdRecord() {
JpaOperations jpaOperations = getJpaOperations(entityManager);
String query = "select * from Student";
List<?> results = jpaOperations.getResultListForNativeQuery(query, StudentDomain.class, null, 2, 0);
assertEquals(1, results.size());
}
public void testGetAllStudentsUsingNamedQueryFromThirdRecord() {
JpaOperations jpaOperations = getJpaOperations(entityManager);
List<?> results = jpaOperations.getResultListForNamedQuery("selectAllStudents", null, 2, 0);
assertEquals(1, results.size());
}
public void testGetAllStudentsUsingJPAQueryFromThirdRecord() {
JpaOperations jpaOperations = getJpaOperations(entityManager);
String query = "select s from Student s";
List<?> results = jpaOperations.getResultListForQuery(query, null, 2, 0);
assertEquals(1, results.size());
}
}

View File

@@ -15,6 +15,7 @@ package org.springframework.integration.jpa.core;
import static org.mockito.Mockito.mock;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManager;
@@ -25,6 +26,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.integration.Message;
import org.springframework.integration.jpa.support.JpaParameter;
import org.springframework.integration.jpa.support.parametersource.ExpressionEvaluatingParameterSourceFactory;
@@ -224,4 +226,48 @@ public class JpaExecutorTests {
}
@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.afterPropertiesSet();
List<?> results = (List<?>)jpaExecutor.poll(MessageBuilder.withPayload("").build());
Assert.assertNotNull(results);
Assert.assertEquals(1, results.size());
}
@Test
public void testResultStartingFromThirdRecordForNativeQuery() throws Exception {
final JpaExecutor jpaExecutor = new JpaExecutor(entityManager);
jpaExecutor.setNativeQuery("select * from Student s");
jpaExecutor.setFirstResultExpression(new LiteralExpression("2"));
jpaExecutor.afterPropertiesSet();
List<?> results = (List<?>)jpaExecutor.poll(MessageBuilder.withPayload("").build());
Assert.assertNotNull(results);
Assert.assertEquals(1, results.size());
}
@Test
public void testResultStartingFromThirdRecordForNamedQuery() throws Exception {
final JpaExecutor jpaExecutor = new JpaExecutor(entityManager);
jpaExecutor.setNamedQuery("selectAllStudents");
jpaExecutor.setFirstResultExpression(new LiteralExpression("2"));
jpaExecutor.afterPropertiesSet();
List<?> results = (List<?>)jpaExecutor.poll(MessageBuilder.withPayload("").build());
Assert.assertNotNull(results);
Assert.assertEquals(1, results.size());
}
@Test
public void testResultStartingFromThirdRecordUsingEntity() throws Exception {
final JpaExecutor jpaExecutor = new JpaExecutor(entityManager);
jpaExecutor.setEntityClass(StudentDomain.class);
jpaExecutor.setFirstResultExpression(new LiteralExpression("2"));
jpaExecutor.afterPropertiesSet();
List<?> results = (List<?>)jpaExecutor.poll(MessageBuilder.withPayload("").build());
Assert.assertNotNull(results);
Assert.assertEquals(1, results.size());
}
}

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int-jpa="http://www.springframework.org/schema/integration/jpa"
xmlns:int="http://www.springframework.org/schema/integration"
xsi:schemaLocation=
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/jpa http://www.springframework.org/schema/integration/jpa/spring-integration-jpa.xsd">
<import resource="classpath:/hibernateJpa-context.xml" />
<int:channel id="in"/>
<int:channel id="out"/>
<int-jpa:retrieving-outbound-gateway
id="retrievingGateway"
entity-manager-factory="entityManagerFactory"
auto-startup="true"
entity-class="org.springframework.integration.jpa.test.entity.StudentDomain"
request-channel="in"
reply-channel="out"
first-result-expression="payload"
reply-timeout="100"/>
</beans>

View File

@@ -0,0 +1,72 @@
/*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.jpa.outbound;
import static org.junit.Assert.assertEquals;
import java.util.List;
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.Message;
import org.springframework.integration.MessagingException;
import org.springframework.integration.core.MessageHandler;
import org.springframework.integration.core.SubscribableChannel;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* The test cases for testing out a complete flow of the JPA gateways/adapters with all
* the components integrated.
*
* @author Amol Nayak
* @since 3.0
*
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class JpaOutboundGatewayIntegrationTests {
@Autowired
@Qualifier("in")
private SubscribableChannel requestChannel;
@Autowired
@Qualifier("out")
private SubscribableChannel responseChannel;
/**
* Sends a message with the payload as a integer representing the start number in the result
* set.
* @throws Exception
*/
@Test
public void retrieveFromSecondRecord() throws Exception {
responseChannel.subscribe(new MessageHandler() {
@SuppressWarnings("rawtypes")
@Override
public void handleMessage(Message<?> message) throws MessagingException {
assertEquals(1, ((List)message.getPayload()).size());
}
});
Message<Integer> message = MessageBuilder.withPayload(2).build();
requestChannel.send(message);
}
}

View File

@@ -30,6 +30,7 @@
<int:method name="persistStudentUsingMerge" request-channel="persistStudentUsingMergeChannel" />
<int:method name="getStudent2" request-channel="retrievingGatewayInsideChain" />
<int:method name="persistStudent2" request-channel="updatingGatewayInsideChain" />
<int:method name="getAllStudentsFromGivenRecord" request-channel="getStudentsFromGivenRecordChannel"/>
</int:gateway>
<int:channel id="studentReplyChannel"/>
@@ -43,6 +44,7 @@
<int:channel id="getStudentEndpointWithExceptionChannel"/>
<int:channel id="retrievingGatewayInsideChain"/>
<int:channel id="updatingGatewayInsideChain"/>
<int:channel id="getStudentsFromGivenRecordChannel"/>
<bean id="deleteStudentEndpoint"
class="org.springframework.integration.endpoint.EventDrivenConsumer">
@@ -198,6 +200,26 @@
</constructor-arg>
</bean>
<bean id="getStudentsFromGivenFromRecord"
class="org.springframework.integration.endpoint.EventDrivenConsumer">
<constructor-arg name="inputChannel" ref="getStudentsFromGivenRecordChannel"/>
<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')}"/>
</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

@@ -14,11 +14,11 @@ package org.springframework.integration.jpa.outbound;
import java.util.List;
import org.junit.Assert;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.MessagingException;
import org.springframework.integration.jpa.test.JpaTestUtils;
@@ -33,6 +33,8 @@ import org.springframework.transaction.annotation.Transactional;
*
* @author Gunnar Hillert
* @author Artem Bilan
* @author Amol Nayak
*
* @since 2.2
*
*/
@@ -58,6 +60,13 @@ public class JpaOutboundGatewayTests {
Assert.assertNotNull(student);
}
@Test
public void getAllStudentsStartingFromGivenRecord() {
List<?> students = studentService.getAllStudentsFromGivenRecord(1);
Assert.assertNotNull(students);
Assert.assertEquals(2, students.size());
}
@Test
public void deleteNonExistingStudent() {

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
@@ -29,6 +29,8 @@ public interface StudentService {
@Payload("new java.util.Date()")
List<StudentDomain> getAllStudents();
List<StudentDomain> getAllStudentsFromGivenRecord(int recordNumber);
StudentDomain persistStudent(StudentDomain student);
StudentDomain persistStudentUsingMerge(StudentDomain studentToPersist);

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.
@@ -36,10 +36,13 @@ import javax.persistence.TemporalType;
* @author Amol Nayak
* @author Gunnar Hillert
*
* @since 2.2
*
*/
@Entity(name="Student")
@Table(name="Student")
@NamedQueries({
@NamedQuery(name="selectAllStudents", query="select s from Student s"),
@NamedQuery(name="selectStudent", query="select s from Student s where s.lastName = 'Last One'"),
@NamedQuery(name="updateStudent", query="update Student s set s.lastName = :lastName, s.lastUpdated = :lastUpdated where s.rollNumber in (select max(a.rollNumber) from Student a)")
})

View File

@@ -1138,6 +1138,8 @@ public class Student {
jpa-operations=""
jpa-query=""
max-number-of-results="" ]]><co id="outGateMaxNumOfResults"/><![CDATA[
first-result="" ]]><co id="outGateFirstResult"/><![CDATA[
first-result-expression="" ]]><co id="outGateFirstResultExpression"/><![CDATA[
named-query=""
native-query=""
order=""
@@ -1168,6 +1170,20 @@ public class Student {
all the possible records are selected by given query.<emphasis>Optional</emphasis>.
</para>
</callout>
<callout arearefs="outGateFirstResult">
<para>
This non zero, non negative integer value tells the adapter the first record from which the
results are to be retrieved This attribute is mutually exclusive to <code>first-result-expression</code>.
This attribute is introduced since version 3.0. <emphasis>Optional</emphasis>.
</para>
</callout>
<callout arearefs="outGateFirstResultExpression">
<para>
This expression is evaluated against the message to find the position of first record in the
result set to be retrieved This attribute is mutually exclusive to <code>first-result</code>.
This attribute is introduced since version 3.0. <emphasis>Optional</emphasis>.
</para>
</callout>
</calloutlist>
<important>
<para>

View File

@@ -452,5 +452,14 @@
least 1.
</para>
</section>
<section id="3.0-jpa-first-result">
<title>JPA Adapters: first-result attribute</title>
<para>
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"/>
</para>
</section>
</section>
</chapter>