INT-4202: Fix StoredProcOutboundGateway NPE

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

The `StoredProcOutboundGateway` uses `MessageBuilder` directly for procedure result without any conditions.
If procedure result is `null`, `MessageBuilder.withPayload` throws `java.lang.IllegalArgumentException: payload must not be null`.

* Since the super `AbstractReplyProducingMessageHandler` class takes care about `null` reply properly via its `requiresReply` property and really uses `MessageBuilder`
 for reply, just fix `StoredProcOutboundGateway` to return procedure result as is from the `handleRequestMessage()` implementation
* Fix some typos and code style
* Increase some JDBC tests performance changing `Thread.sleep()` solution to proper `PollableChannel.receive()` or iterations over expected result

**Cherry-pick to 4.3.x**

Update `@Copyright` to 2017

Make `StoredProcOutboundGateway` as `requiresReply = true` by default

Revert `setRequiresReply(true)` change

Specify `setRequiresReply(true)` only if `expectSingleResult == true` and `setRequiresReply()` hasn't been called explicitly

Conflicts:
	spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/StoredProcOutboundGateway.java
	spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcMessageStoreChannelIntegrationTests.java
	spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/StoredProcOutboundGatewayParserTests.java
Resolved.
This commit is contained in:
Artem Bilan
2017-01-03 11:40:39 -05:00
committed by Gary Russell
parent 85a825cd75
commit 0c2ba3f761
14 changed files with 143 additions and 103 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
@@ -26,7 +26,11 @@ import org.springframework.messaging.MessagingException;
import org.springframework.util.Assert;
/**
* An {@link AbstractReplyProducingMessageHandler} implementation for performing
* RDBMS stored procedures which return results.
*
* @author Gunnar Hillert
* @author Artem Bilan
*
* @since 2.1
*/
@@ -36,17 +40,45 @@ public class StoredProcOutboundGateway extends AbstractReplyProducingMessageHand
private volatile boolean expectSingleResult = false;
private boolean requiresReplyExplicitlySet;
/**
* Constructor taking {@link StoredProcExecutor}.
*
* @param storedProcExecutor Must not be null.
*
*/
public StoredProcOutboundGateway(StoredProcExecutor storedProcExecutor) {
Assert.notNull(storedProcExecutor, "storedProcExecutor must not be null.");
this.executor = storedProcExecutor;
}
@Override
public void setRequiresReply(boolean requiresReply) {
super.setRequiresReply(requiresReply);
this.requiresReplyExplicitlySet = true;
}
/**
* This parameter indicates that only one result object shall be returned from
* the Stored Procedure/Function Call. If set to {@code true}, a {@code resultMap} that contains
* only 1 element, will have that 1 element extracted and returned as payload.
* <p> If the {@code resultMap} contains more than 1 element and {@code expectSingleResult == true},
* then a {@link MessagingException} is thrown.
* <p> Otherwise the complete {@code resultMap} is returned as the {@link Message} payload.
* <p> Important Note: Several databases such as H2 are not fully supported.
* The H2 database, for example, does not fully support the {@link CallableStatement}
* semantics and when executing function calls against H2, a result list is
* returned rather than a single value.
* <p> Therefore, even if you set {@code expectSingleResult = true}, you may end up with
* a collection being returned.
* <p> When set to {@code true}, a {@link #setRequiresReply(boolean)} is called
* with {@code true} as well, indicating that exactly single result is expected
* and {@code null} isn't appropriate value.
* A {@link org.springframework.integration.handler.ReplyRequiredException} is thrown
* in case of {@code null} result.
* @param expectSingleResult true if a single result is expected.
*/
public void setExpectSingleResult(boolean expectSingleResult) {
this.expectSingleResult = expectSingleResult;
}
@Override
@@ -55,8 +87,15 @@ public class StoredProcOutboundGateway extends AbstractReplyProducingMessageHand
}
@Override
protected Object handleRequestMessage(Message<?> requestMessage) {
protected void doInit() {
super.doInit();
if (!this.requiresReplyExplicitlySet) {
setRequiresReply(this.expectSingleResult);
}
}
@Override
protected Object handleRequestMessage(Message<?> requestMessage) {
Map<String, Object> resultMap = this.executor.executeStoredProcedure(requestMessage);
final Object payload;
@@ -65,7 +104,6 @@ public class StoredProcOutboundGateway extends AbstractReplyProducingMessageHand
return null;
}
else {
if (this.expectSingleResult && resultMap.size() == 1) {
payload = resultMap.values().iterator().next();
}
@@ -73,41 +111,16 @@ public class StoredProcOutboundGateway extends AbstractReplyProducingMessageHand
throw new MessageHandlingException(requestMessage,
"Stored Procedure/Function call returned more than "
+ "1 result object and expectSingleResult was 'true'. ");
+ "1 result object and expectSingleResult was 'true'. ");
}
else {
payload = resultMap;
}
}
return this.getMessageBuilderFactory().withPayload(payload).copyHeaders(requestMessage.getHeaders()).build();
return payload;
}
/**
* This parameter indicates that only one result object shall be returned from
* the Stored Procedure/Function Call. If set to true, a resultMap that contains
* only 1 element, will have that 1 element extracted and returned as payload.
*
* If the resultMap contains more than 1 element and expectSingleResult is true,
* then a {@link MessagingException} is thrown.
*
* Otherwise the complete resultMap is returned as the {@link Message} payload.
*
* Important Note: Several databases such as H2 are not fully supported.
* The H2 database, for example, does not fully support the {@link CallableStatement}
* semantics and when executing function calls against H2, a result list is
* returned rather than a single value.
*
* Therefore, even if you set expectSingleResult = true, you may end up with
* a collection being returned.
*
* @param expectSingleResult true if a single result is expected.
*/
public void setExpectSingleResult(boolean expectSingleResult) {
this.expectSingleResult = expectSingleResult;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
@@ -51,11 +51,11 @@ public class StoredProcOutboundGatewayParser extends AbstractConsumerEndpointPar
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(storedProcExecutorBuilder, element, "sql-parameter-source-factory");
IntegrationNamespaceUtils.setValueIfAttributeDefined(storedProcExecutorBuilder, element, "skip-undeclared-results");
final ManagedMap<String, BeanMetadataElement> returningResultsetMap =
final ManagedMap<String, BeanMetadataElement> returningResultSetMap =
StoredProcParserUtils.getReturningResultsetBeanDefinitions(element, parserContext);
if (!returningResultsetMap.isEmpty()) {
storedProcExecutorBuilder.addPropertyValue("returningResultSetRowMappers", returningResultsetMap);
if (!returningResultSetMap.isEmpty()) {
storedProcExecutorBuilder.addPropertyValue("returningResultSetRowMappers", returningResultSetMap);
}
final AbstractBeanDefinition storedProcExecutorBuilderBeanDefinition = storedProcExecutorBuilder.getBeanDefinition();

View File

@@ -656,7 +656,7 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto
*/
public void removeFromIdCache(String messageId) {
if (logger.isDebugEnabled()) {
logger.debug("Removing Message Id:" + messageId);
logger.debug("Removing Message Id: " + messageId);
}
this.idCacheWriteLock.lock();
try {

View File

@@ -963,7 +963,7 @@
<xsd:union memberTypes="xsd:boolean xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="requires-reply" type="xsd:string" use="optional" default="false">
<xsd:attribute name="requires-reply" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
Specify whether this outbound gateway must return a non-null value. This value is

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.

View File

@@ -2,17 +2,10 @@
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:int-jdbc="http://www.springframework.org/schema/integration/jdbc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:p="http://www.springframework.org/schema/p"
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
xsi:schemaLocation="http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/jdbc http://www.springframework.org/schema/integration/jdbc/spring-integration-jdbc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<import resource="classpath:derby-stored-procedures-setup-context.xml"/>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
@@ -21,6 +21,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.sql.CallableStatement;
import java.util.Collection;
@@ -38,6 +39,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.handler.ReplyRequiredException;
import org.springframework.integration.jdbc.config.JdbcTypesEnum;
import org.springframework.integration.jdbc.storedproc.User;
import org.springframework.integration.support.MessageBuilder;
@@ -149,6 +151,7 @@ public class StoredProcOutboundGatewayWithSpelIntegrationTests {
@Test
@Transactional
public void testInt2865SqlReturnType() throws Exception {
Mockito.reset(this.clobSqlReturnType);
Message<String> testMessage = MessageBuilder.withPayload("TEST").setHeader("FOO", "BAR").build();
String messageId = testMessage.getHeaders().getId().toString();
String jsonMessage = new JsonOutboundMessageMapper().fromMessage(testMessage);
@@ -168,6 +171,17 @@ public class StoredProcOutboundGatewayWithSpelIntegrationTests {
Mockito.eq(2), Mockito.eq(JdbcTypesEnum.CLOB.getCode()), Mockito.eq((String) null));
}
@Test
public void testNoIllegalArgumentButRequiresReplyException() {
try {
this.getMessageChannel.send(new GenericMessage<String>("foo"));
fail("ReplyRequiredException expected");
}
catch (Exception e) {
assertThat(e, instanceOf(ReplyRequiredException.class));
}
}
static class Counter {
private final AtomicInteger count = new AtomicInteger();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
@@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
@@ -33,6 +34,7 @@ import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvi
import org.springframework.integration.jdbc.JdbcMessageHandler;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.jdbc.core.ColumnMapRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
@@ -117,8 +119,18 @@ public class JdbcMessageHandlerParserTests {
MessageChannel target = context.getBean("target", MessageChannel.class);
Message<?> message = MessageBuilder.withPayload("foo").setHeader("business.key", "FOO").build();
target.send(message);
Thread.sleep(2000);
Map<String, Object> map = (context.getBean("jdbcTemplate", JdbcTemplate.class)).queryForMap("SELECT * from FOOW");
JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
int n = 0;
List<Map<String, Object>> result = jdbcTemplate.query("SELECT * from FOOW", new ColumnMapRowMapper());
while (result.isEmpty() && n++ < 100) {
Thread.sleep(100);
result = jdbcTemplate.query("SELECT * from FOOW", new ColumnMapRowMapper());
}
assertTrue(n < 100);
assertEquals(1, result.size());
Map<String, Object> map = result.get(0);
assertEquals("Wrong id", "FOO", map.get("ID"));
assertEquals("Wrong id", "foo", map.get("name"));
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
@@ -50,6 +50,8 @@ import org.springframework.transaction.support.TransactionTemplate;
/**
* @author David Syer
* @author Gary Russell
* @author Artem Bilan
*
* @since 2.0
*
*/
@@ -69,6 +71,7 @@ public class JdbcPollingChannelAdapterParserTests {
public void testNoAutoStartupInboundChannelAdapter() {
setUp("pollingNoAutoStartupJdbcInboundChannelAdapterTest.xml", getClass());
this.jdbcTemplate.update("insert into item values(1,'',2)");
messagingTemplate.setReceiveTimeout(1);
Message<?> message = messagingTemplate.receive();
assertNull("Message found ", message);
}
@@ -94,8 +97,9 @@ public class JdbcPollingChannelAdapterParserTests {
this.jdbcTemplate.update("insert into item values(1,'',2)");
Message<?> message = messagingTemplate.receive();
assertNotNull(message);
messagingTemplate.setReceiveTimeout(1);
message = messagingTemplate.receive();
assertNull(messagingTemplate.receive());
assertNull(message);
}
@Test
@@ -104,8 +108,9 @@ public class JdbcPollingChannelAdapterParserTests {
this.jdbcTemplate.update("insert into item values(1,'',2)");
Message<?> message = messagingTemplate.receive();
assertNotNull(message);
messagingTemplate.setReceiveTimeout(1);
message = messagingTemplate.receive();
assertNull(messagingTemplate.receive());
assertNull(message);
}
@Test

View File

@@ -171,7 +171,7 @@ public class StoredProcOutboundGatewayParserTests {
@SuppressWarnings("unchecked")
@Test
public void testProcedurepParametersAreSet() throws Exception {
public void testProcedureParametersAreSet() throws Exception {
setUp("storedProcOutboundGatewayParserTest.xml", getClass());
DirectFieldAccessor accessor = new DirectFieldAccessor(this.outboundGateway);
@@ -200,7 +200,7 @@ public class StoredProcOutboundGatewayParserTests {
assertEquals("kenny", parameter1.getValue());
assertEquals("Who killed Kenny?", parameter2.getValue());
assertNull(parameter3.getValue());
assertEquals(Integer.valueOf(30), parameter4.getValue());
assertEquals(30, parameter4.getValue());
assertNull(parameter1.getExpression());
assertNull(parameter2.getExpression());
@@ -273,11 +273,8 @@ public class StoredProcOutboundGatewayParserTests {
assertEquals("SqlType is ", Types.INTEGER, parameter3.getSqlType());
assertEquals("SqlType is ", Types.VARCHAR, parameter4.getSqlType());
assertTrue(parameter1 instanceof SqlParameter);
assertTrue(parameter2 instanceof SqlOutParameter);
assertTrue(parameter3 instanceof SqlInOutParameter);
assertTrue(parameter4 instanceof SqlParameter);
}
@Test

View File

@@ -1,39 +1,41 @@
<?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-jdbc="http://www.springframework.org/schema/integration/jdbc"
xmlns:int="http://www.springframework.org/schema/integration"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:int-jdbc="http://www.springframework.org/schema/integration/jdbc"
xmlns:int="http://www.springframework.org/schema/integration"
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/integration/jdbc http://www.springframework.org/schema/integration/jdbc/spring-integration-jdbc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<int:channel id="requestChannel" />
<int:channel id="replyChannel" />
<int:channel id="requestChannel"/>
<int:channel id="replyChannel"/>
<jdbc:embedded-database id="datasource" type="HSQL" />
<jdbc:embedded-database id="datasource" type="HSQL"/>
<int-jdbc:stored-proc-outbound-gateway
request-channel="requestChannel" stored-procedure-name="GET_PRIME_NUMBERS"
data-source="datasource" auto-startup="true" id="storedProcedureOutboundGateway"
ignore-column-meta-data="false" is-function="false"
skip-undeclared-results="false" order="2" reply-channel="replyChannel"
reply-timeout="555" return-value-required="false">
<int-jdbc:stored-proc-outbound-gateway
request-channel="requestChannel" stored-procedure-name="GET_PRIME_NUMBERS"
data-source="datasource" auto-startup="true" id="storedProcedureOutboundGateway"
ignore-column-meta-data="false" is-function="false"
skip-undeclared-results="false" order="2" reply-channel="replyChannel"
reply-timeout="555" return-value-required="false">
<int-jdbc:sql-parameter-definition name="username" direction="IN" type="VARCHAR" />
<int-jdbc:sql-parameter-definition name="password" direction="OUT" />
<int-jdbc:sql-parameter-definition name="age" direction="INOUT" type="INTEGER" scale="5" />
<int-jdbc:sql-parameter-definition name="description" />
<int-jdbc:parameter name="username" value="kenny" type="java.lang.String" />
<int-jdbc:parameter name="description" value="Who killed Kenny?" />
<int-jdbc:parameter name="password" expression="payload.username" />
<int-jdbc:parameter name="age" value="30" type="java.lang.Integer" />
<int-jdbc:returning-resultset name="out" row-mapper="org.springframework.integration.jdbc.storedproc.PrimeMapper"/>
<int-jdbc:sql-parameter-definition name="username" direction="IN" type="VARCHAR"/>
<int-jdbc:sql-parameter-definition name="password" direction="OUT"/>
<int-jdbc:sql-parameter-definition name="age" direction="INOUT" type="INTEGER" scale="5"/>
<int-jdbc:sql-parameter-definition name="description"/>
<int-jdbc:parameter name="username" value="kenny" type="java.lang.String"/>
<int-jdbc:parameter name="description" value="Who killed Kenny?"/>
<int-jdbc:parameter name="password" expression="payload.username"/>
<int-jdbc:parameter name="age" value="30" type="java.lang.Integer"/>
<int-jdbc:returning-resultset name="out"
row-mapper="org.springframework.integration.jdbc.storedproc.PrimeMapper"/>
<int-jdbc:request-handler-advice-chain>
<bean class="org.springframework.integration.jdbc.config.StoredProcOutboundGatewayParserTests$FooAdvice" />
</int-jdbc:request-handler-advice-chain>
</int-jdbc:stored-proc-outbound-gateway>
<int-jdbc:request-handler-advice-chain>
<bean class="org.springframework.integration.jdbc.config.StoredProcOutboundGatewayParserTests$FooAdvice"/>
</int-jdbc:request-handler-advice-chain>
</int-jdbc:stored-proc-outbound-gateway>
<int:poller default="true" fixed-rate="10000"/>
<int:poller default="true" fixed-rate="10000" />
</beans>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
@@ -101,6 +101,9 @@ public abstract class AbstractTxTimeoutMessageStoreTests {
@Autowired
protected PollableChannel priorityChannel;
@Autowired
protected PollableChannel afterTxChannel;
@Test
public void test() throws InterruptedException {
@@ -131,7 +134,10 @@ public abstract class AbstractTxTimeoutMessageStoreTests {
Assert.assertTrue(String.format("Countdown latch did not count down from " +
"%s to 0 in %sms.", maxMessages, maxWaitTime), testService.await(maxWaitTime));
Thread.sleep(2000);
for (int i = 0; i < maxMessages; i++) {
Message<?> afterTxMessage = this.afterTxChannel.receive(10000);
assertNotNull(afterTxMessage);
}
Assert.assertEquals(Integer.valueOf(0), Integer.valueOf(jdbcChannelMessageStore.getSizeOfIdCache()));
Assert.assertEquals(Integer.valueOf(maxMessages), Integer.valueOf(testService.getSeenMessages().size()));

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
@@ -69,7 +69,7 @@ public class TestService {
if (this.threadSleep > 0) {
try {
Thread.sleep(2000);
Thread.sleep(10);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();

View File

@@ -2,22 +2,20 @@
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-jdbc="http://www.springframework.org/schema/integration/jdbc"
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
xsi:schemaLocation="http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/task/spring-jdbc.xsd
http://www.springframework.org/schema/integration/jdbc http://www.springframework.org/schema/integration/jdbc/spring-integration-jdbc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<int:transaction-synchronization-factory id="syncFactory">
<int:after-commit expression="@store.removeFromIdCache(headers.id.toString())" />
<int:after-rollback expression="@store.removeFromIdCache(headers.id.toString())"/>
<int:before-commit expression="@store.removeFromIdCache(headers.id.toString())"/>
<int:after-commit channel="afterTxChannel"/>
</int:transaction-synchronization-factory>
<int:channel id="afterTxChannel">
<int:queue/>
</int:channel>
<task:executor id="pool" pool-size="10" queue-capacity="10" rejection-policy="CALLER_RUNS" />
<bean id="store" class="org.springframework.integration.jdbc.store.JdbcChannelMessageStore">