INT-3772: Add MessagePreparedStatementCallback

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

INT-3722: Polishing

PR Comments and conflicts fixes
This commit is contained in:
Artem Bilan
2015-08-10 18:07:18 -04:00
committed by Gary Russell
parent 057c004e6c
commit 9ff5ab789a
13 changed files with 344 additions and 39 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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
@@ -13,24 +13,35 @@
package org.springframework.integration.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.integration.MessageRejectedException;
import org.springframework.integration.handler.AbstractMessageHandler;
import org.springframework.jdbc.core.ColumnMapRowMapper;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.PreparedStatementCallback;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowMapperResultSetExtractor;
import org.springframework.jdbc.core.namedparam.EmptySqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageDeliveryException;
import org.springframework.messaging.MessageHandlingException;
import org.springframework.util.Assert;
import org.springframework.util.LinkedCaseInsensitiveMap;
/**
@@ -46,18 +57,33 @@ import org.springframework.util.LinkedCaseInsensitiveMap;
* headers with dotted names (e.g. <code>business.id</code>)
*
* @author Dave Syer
* @author Artem Bilan
* @since 2.0
*/
public class JdbcMessageHandler extends AbstractMessageHandler {
private final ResultSetExtractor<List<Map<String, Object>>> generatedKeysResultSetExtractor =
new RowMapperResultSetExtractor<Map<String, Object>>(new ColumnMapRowMapper(), 1);
private final NamedParameterJdbcOperations jdbcOperations;
private final PreparedStatementCreator generatedKeysStatementCreator = new PreparedStatementCreator() {
@Override
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
return con.prepareStatement(JdbcMessageHandler.this.updateSql, Statement.RETURN_GENERATED_KEYS);
}
};
private volatile String updateSql;
private volatile SqlParameterSourceFactory sqlParameterSourceFactory = new BeanPropertySqlParameterSourceFactory();
private volatile SqlParameterSourceFactory sqlParameterSourceFactory;
private volatile boolean keysGenerated;
private MessagePreparedStatementSetter preparedStatementSetter;
/**
* Constructor taking {@link DataSource} from which the DB Connection can be obtained and the select query to
* execute to retrieve new rows.
@@ -83,7 +109,8 @@ public class JdbcMessageHandler extends AbstractMessageHandler {
}
/**
* Flag to indicate that the update query is an insert with autogenerated keys, which will be logged at debug level.
* Flag to indicate that the update query is an insert with auto-generated keys,
* which will be logged at debug level.
* @param keysGenerated the flag value to set
*/
public void setKeysGenerated(boolean keysGenerated) {
@@ -98,41 +125,101 @@ public class JdbcMessageHandler extends AbstractMessageHandler {
this.sqlParameterSourceFactory = sqlParameterSourceFactory;
}
/**
* Specify a {@link MessagePreparedStatementSetter} to populate parameters on the
* {@link PreparedStatement} with the {@link Message} context.
* <p>This is a low-level alternative to the {@link SqlParameterSourceFactory}.
* @param preparedStatementSetter the {@link MessagePreparedStatementSetter} to set.
* @since 4.2
*/
public void setPreparedStatementSetter(MessagePreparedStatementSetter preparedStatementSetter) {
this.preparedStatementSetter = preparedStatementSetter;
}
@Override
public String getComponentType() {
return "jdbc:outbound-channel-adapter";
}
@Override
protected void onInit() throws Exception {
super.onInit();
Assert.state(!(this.sqlParameterSourceFactory != null && this.preparedStatementSetter != null),
"'sqlParameterSourceFactory' and 'preparedStatementSetter' are mutually exclusive.");
if (this.sqlParameterSourceFactory == null && this.preparedStatementSetter == null) {
this.sqlParameterSourceFactory = new BeanPropertySqlParameterSourceFactory();
}
}
/**
* Executes the update, passing the message into the {@link SqlParameterSourceFactory}.
*/
@Override
protected void handleMessageInternal(Message<?> message) throws MessageRejectedException, MessageHandlingException,
MessageDeliveryException {
protected void handleMessageInternal(Message<?> message) throws Exception {
List<? extends Map<String, Object>> keys = executeUpdateQuery(message, keysGenerated);
if (logger.isDebugEnabled() && !keys.isEmpty()) {
logger.debug("Generated keys: "+keys);
if (!keys.isEmpty() && logger.isDebugEnabled()) {
logger.debug("Generated keys: " + keys);
}
}
protected List<? extends Map<String, Object>> executeUpdateQuery(Object obj, boolean keysGenerated) {
SqlParameterSource updateParameterSource = new MapSqlParameterSource();
if (this.sqlParameterSourceFactory != null) {
updateParameterSource = this.sqlParameterSourceFactory.createParameterSource(obj);
protected List<? extends Map<String, Object>> executeUpdateQuery(final Message<?> message, boolean keysGenerated) {
SqlParameterSource updateParameterSource = EmptySqlParameterSource.INSTANCE;
if (this.preparedStatementSetter == null) {
if (this.sqlParameterSourceFactory != null) {
updateParameterSource = this.sqlParameterSourceFactory.createParameterSource(message);
}
}
if (keysGenerated) {
KeyHolder keyHolder = new GeneratedKeyHolder();
this.jdbcOperations.update(this.updateSql, updateParameterSource,
keyHolder);
return keyHolder.getKeyList();
if (this.preparedStatementSetter != null) {
return this.jdbcOperations.getJdbcOperations().execute(this.generatedKeysStatementCreator,
new PreparedStatementCallback<List<Map<String, Object>>>() {
@Override
public List<Map<String, Object>> doInPreparedStatement(PreparedStatement ps)
throws SQLException {
preparedStatementSetter.setValues(ps, message);
ps.executeUpdate();
ResultSet keys = ps.getGeneratedKeys();
if (keys != null) {
try {
return generatedKeysResultSetExtractor.extractData(keys);
}
finally {
JdbcUtils.closeResultSet(keys);
}
}
return new LinkedList<Map<String, Object>>();
}
});
}
else {
KeyHolder keyHolder = new GeneratedKeyHolder();
this.jdbcOperations.update(this.updateSql, updateParameterSource, keyHolder);
return keyHolder.getKeyList();
}
}
else {
int updated = this.jdbcOperations.update(this.updateSql, updateParameterSource);
int updated;
if (this.preparedStatementSetter != null) {
updated = this.jdbcOperations.getJdbcOperations().update(this.updateSql,
new PreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps) throws SQLException {
JdbcMessageHandler.this.preparedStatementSetter.setValues(ps, message);
}
});
}
else {
updated = this.jdbcOperations.update(this.updateSql, updateParameterSource);
}
LinkedCaseInsensitiveMap<Object> map = new LinkedCaseInsensitiveMap<Object>();
map.put("UPDATED", updated);
return Collections.singletonList(map);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2015 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.
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.jdbc;
import java.util.Collections;
@@ -33,6 +34,7 @@ import org.springframework.util.StringUtils;
/**
* @author Dave Syer
* @author Gunnar Hillert
* @author Artem Bilan
*
* @since 2.0
*/
@@ -169,7 +171,7 @@ public class JdbcOutboundGateway extends AbstractReplyProducingMessageHandler im
}
/**
* Flag to indicate that the update query is an insert with autogenerated keys, which will be logged at debug level.
* Flag to indicate that the update query is an insert with auto-generated keys, which will be logged at debug level.
* @param keysGenerated the flag value to set
*/
public void setKeysGenerated(boolean keysGenerated) {
@@ -177,10 +179,15 @@ public class JdbcOutboundGateway extends AbstractReplyProducingMessageHandler im
}
public void setRequestSqlParameterSourceFactory(SqlParameterSourceFactory sqlParameterSourceFactory) {
Assert.notNull(this.handler);
Assert.notNull(this.handler, "'handler' cannot be null");
this.handler.setSqlParameterSourceFactory(sqlParameterSourceFactory);
}
public void setRequestPreparedStatementSetter(MessagePreparedStatementSetter requestPreparedStatementSetter) {
Assert.notNull(this.handler, "'handler' cannot be null");
this.handler.setPreparedStatementSetter(requestPreparedStatementSetter);
}
public void setReplySqlParameterSourceFactory(SqlParameterSourceFactory sqlParameterSourceFactory) {
this.sqlParameterSourceFactory = sqlParameterSourceFactory;
this.sqlParameterSourceFactorySet = true;

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2015 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.jdbc;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.messaging.Message;
/**
* The callback to be used with the {@link JdbcMessageHandler}
* as an alternative to the {@link SqlParameterSourceFactory}.
* <p>
* Plays the same role as standard {@link PreparedStatementSetter},
* but with {@code Message<?> requestMessage} context during {@code handleMessage}
* process in the {@link JdbcMessageHandler}.
*
* @author Artem Bilan
* @since 4.2
* @see PreparedStatementSetter
*/
public interface MessagePreparedStatementSetter {
void setValues(PreparedStatement ps, Message<?> requestMessage) throws SQLException;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2015 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
@@ -13,6 +13,8 @@
package org.springframework.integration.jdbc.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
@@ -21,10 +23,10 @@ import org.springframework.integration.config.xml.AbstractOutboundChannelAdapter
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.jdbc.JdbcMessageHandler;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* @author Dave Syer
* @author Artem Bilan
* @since 2.0
*
*/
@@ -67,6 +69,7 @@ public class JdbcMessageHandlerParser extends AbstractOutboundChannelAdapterPars
builder.addConstructorArgReference(jdbcOperationsRef);
}
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "sql-parameter-source-factory");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "prepared-statement-setter");
builder.addConstructorArgValue(query);
return builder.getBeanDefinition();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2015 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
@@ -13,13 +13,14 @@
package org.springframework.integration.jdbc.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractConsumerEndpointParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.jdbc.JdbcOutboundGateway;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* @author Dave Syer
@@ -64,6 +65,8 @@ public class JdbcOutboundGatewayParser extends AbstractConsumerEndpointParser {
.setReferenceIfAttributeDefined(builder, element, "reply-sql-parameter-source-factory");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element,
"request-sql-parameter-source-factory");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element,
"request-prepared-statement-setter");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "row-mapper");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "max-rows-per-poll");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "keys-generated");

View File

@@ -243,6 +243,7 @@
default factory creates a bean
property parameter source so the query can specify named
parameters like :payload and :headers[foo].
This attribute is mutually exclusive with the 'prepared-statement-setter'.
</xsd:documentation>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.jdbc.SqlParameterSourceFactory" />
@@ -250,6 +251,19 @@
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="prepared-statement-setter" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<xsd:documentation>
Reference to a MessagePreparedStatementSetter.
This attribute is mutually exclusive with the 'sql-parameter-source-factory'.
</xsd:documentation>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.jdbc.MessagePreparedStatementSetter" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="keys-generated" type="xsd:boolean">
<xsd:annotation>
<xsd:appinfo>
@@ -362,6 +376,7 @@
default factory creates a bean
property parameter source so the query can specify named
parameters like :payload and :headers[foo].
This attribute is mutually exclusive with the 'request-prepared-statement-setter'.
</xsd:documentation>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.jdbc.SqlParameterSourceFactory" />
@@ -369,6 +384,19 @@
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="request-prepared-statement-setter" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<xsd:documentation>
Reference to a MessagePreparedStatementSetter.
This attribute is mutually exclusive with the 'request-sql-parameter-source-factory'.
</xsd:documentation>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.jdbc.MessagePreparedStatementSetter" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="reply-sql-parameter-source-factory" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2015 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.
@@ -17,8 +17,12 @@
package org.springframework.integration.jdbc;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.After;
import org.junit.Before;
@@ -34,6 +38,7 @@ import org.springframework.messaging.support.GenericMessage;
/**
* @author Dave Syer
* @author Artem Bilan
*/
public class JdbcMessageHandlerIntegrationTests {
@@ -58,6 +63,7 @@ public class JdbcMessageHandlerIntegrationTests {
@Test
public void testSimpleStaticInsert() {
JdbcMessageHandler handler = new JdbcMessageHandler(jdbcTemplate, "insert into foos (id, status, name) values (1, 0, 'foo')");
handler.afterPropertiesSet();
Message<String> message = new GenericMessage<String>("foo");
handler.handleMessage(message);
Map<String, Object> map = jdbcTemplate.queryForMap("SELECT * FROM FOOS WHERE ID=?", 1);
@@ -69,15 +75,38 @@ public class JdbcMessageHandlerIntegrationTests {
@Test
public void testSimpleDynamicInsert() {
JdbcMessageHandler handler = new JdbcMessageHandler(jdbcTemplate, "insert into foos (id, status, name) values (1, 0, :payload)");
handler.afterPropertiesSet();
Message<String> message = new GenericMessage<String>("foo");
handler.handleMessage(message);
Map<String, Object> map = jdbcTemplate.queryForMap("SELECT * FROM FOOS WHERE ID=?", 1);
assertEquals("Wrong name", "foo", map.get("NAME"));
}
@Test
public void testInsertWithMessagePreparedStatementSetter() {
JdbcMessageHandler handler = new JdbcMessageHandler(jdbcTemplate, "insert into foos (id, status, name) values (1, 0, ?)");
final AtomicBoolean setterInvoked = new AtomicBoolean();
handler.setPreparedStatementSetter(new MessagePreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, Message<?> requestMessage) throws SQLException {
ps.setObject(1, requestMessage.getPayload());
setterInvoked.set(true);
}
});
handler.afterPropertiesSet();
Message<String> message = new GenericMessage<String>("foo");
handler.handleMessage(message);
Map<String, Object> map = jdbcTemplate.queryForMap("SELECT * FROM FOOS WHERE ID=?", 1);
assertEquals("Wrong name", "foo", map.get("NAME"));
assertTrue(setterInvoked.get());
}
@Test
public void testIdHeaderDynamicInsert() {
JdbcMessageHandler handler = new JdbcMessageHandler(jdbcTemplate, "insert into foos (id, status, name) values (:headers[idAsString], 0, :payload)");
handler.afterPropertiesSet();
Message<String> message = new GenericMessage<String>("foo");
String id = message.getHeaders().getId().toString();
message = MessageBuilder.fromMessage(message)
@@ -92,6 +121,7 @@ public class JdbcMessageHandlerIntegrationTests {
@Test
public void testDottedHeaderDynamicInsert() {
JdbcMessageHandler handler = new JdbcMessageHandler(jdbcTemplate, "insert into foos (id, status, name) values (:headers[business.id], 0, :payload)");
handler.afterPropertiesSet();
Message<String> message = MessageBuilder.withPayload("foo").setHeader("business.id", "FOO").build();
handler.handleMessage(message);
String id = message.getHeaders().get("business.id").toString();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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
@@ -10,6 +10,7 @@
* 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.jdbc.config;
import static org.junit.Assert.assertEquals;
@@ -17,6 +18,8 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collections;
import java.util.Map;
@@ -34,12 +37,14 @@ import org.springframework.integration.core.MessagingTemplate;
import org.springframework.integration.endpoint.PollingConsumer;
import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice;
import org.springframework.integration.jdbc.JdbcOutboundGateway;
import org.springframework.integration.jdbc.MessagePreparedStatementSetter;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.support.GenericMessage;
/**
* @author Dave Syer
@@ -84,19 +89,33 @@ public class JdbcOutboundGatewayParserTests {
}
@Test
@SuppressWarnings("unchecked")
public void testKeyGeneration() {
setUp("handlingKeyGenerationJdbcOutboundGatewayTest.xml", getClass());
Message<?> message = MessageBuilder.withPayload(Collections.singletonMap("foo", "bar")).build();
channel.send(message);
Message<?> reply = messagingTemplate.receive();
assertNotNull(reply);
@SuppressWarnings("unchecked")
Map<String, ?> payload = (Map<String, ?>) reply.getPayload();
Object id = payload.get("SCOPE_IDENTITY()");
assertNotNull(id);
Map<String, Object> map = this.jdbcTemplate.queryForMap("SELECT * from BARS");
assertEquals("Wrong id", id, map.get("ID"));
assertEquals("Wrong name", "bar", map.get("name"));
this.jdbcTemplate.execute("DELETE FROM BARS");
MessageChannel setterRequest = this.context.getBean("setterRequest", MessageChannel.class);
setterRequest.send(new GenericMessage<String>("bar2"));
reply = messagingTemplate.receive();
assertNotNull(reply);
payload = (Map<String, ?>) reply.getPayload();
id = payload.get("SCOPE_IDENTITY()");
assertNotNull(id);
map = this.jdbcTemplate.queryForMap("SELECT * from BARS");
assertEquals("Wrong id", id, map.get("ID"));
assertEquals("Wrong name", "bar2", map.get("name"));
}
@Test
@@ -259,4 +278,14 @@ public class JdbcOutboundGatewayParserTests {
}
}
public static class TestMessagePreparedStatementSetter implements MessagePreparedStatementSetter {
@Override
public void setValues(PreparedStatement ps, Message<?> requestMessage) throws SQLException {
ps.setObject(1, requestMessage.getPayload());
}
}
}

View File

@@ -17,6 +17,16 @@
<outbound-gateway update="insert into bars (status, name) values (0, :payload[foo])" request-channel="target"
reply-channel="output" data-source="dataSource" keys-generated="true" />
<beans:bean id="messagePreparedStatementSetter"
class="org.springframework.integration.jdbc.config.JdbcOutboundGatewayParserTests$TestMessagePreparedStatementSetter"/>
<outbound-gateway update="insert into bars (status, name) values (0, ?)"
request-channel="setterRequest"
reply-channel="output"
data-source="dataSource"
request-prepared-statement-setter="messagePreparedStatementSetter"
keys-generated="true" />
<beans:import resource="jdbcOutboundChannelAdapterCommonConfig.xml" />
</beans:beans>

View File

@@ -1,15 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration/jdbc"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:si="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
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">
<beans:bean id="preparedStatementSetter" class="org.mockito.Mockito" factory-method="mock">
<beans:constructor-arg value="org.springframework.integration.jdbc.MessagePreparedStatementSetter"/>
</beans:bean>
<!-- Invalid Config
<outbound-channel-adapter id="invalid"
query="invalid anyway"
data-source="dataSource"
prepared-statement-setter="preparedStatementSetter"
sql-parameter-source-factory="sqlParameterSourceFactory"/>-->
<outbound-channel-adapter query="insert into foos (id, status, name) values (:headers[id], 0, :foo)"
channel="target" data-source="dataSource" sql-parameter-source-factory="sqlParameterSourceFactory"/>

View File

@@ -1,4 +1,4 @@
log4j.rootCategory=INFO, stdout
log4j.rootCategory=WARN, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

View File

@@ -181,6 +181,49 @@ The following example uses a `ExpressionEvaluatingSqlParameterSourceFactory` to
For further information, please also see <<sp-defining-parameter-sources>>
_PreparedStatement Callback_
There are some cases when the flexibility and loose-coupling of `SqlParameterSourceFactory` isn't enough for the target
`PreparedStatement` or we need to do some low-level JDBC work.
The Spring JDBC module provides APIs to configure the execution environment (e.g. `ConnectionCallback`
or `PreparedStatementCreator`) and manipulation of parameter values (e.g. `SqlParameterSource`).
Or even APIs for low level operations, for example `StatementCallback`.
Starting with _Spring Integration 4.2_, the `MessagePreparedStatementSetter` is available to allow
the specification of parameters on the `PreparedStatement` manually, in the `requestMessage` context.
This class plays exactly the same role as `PreparedStatementSetter` in the standard Spring JDBC API.
Actually it is invoked directly from an inline `PreparedStatementSetter` implementation, when the `JdbcMessageHandler`
performs invokes `execute` on the `JdbcTemplate`.
This functional interface option is mutually exclusive with `sqlParameterSourceFactory` and can be used as a more
powerful alternative to populate parameters of the `PreparedStatement` from the `requestMessage`.
For example it is useful when we need to store `File` data to the DataBase `BLOB` column in a stream manner:
[source,java]
----
@Bean
@ServiceActivator(inputChannel = "storeFileChannel")
public MessageHandler jdbcMessageHandler(DataSource dataSource) {
JdbcMessageHandler jdbcMessageHandler = new JdbcMessageHandler(dataSource,
"INSERT INTO imagedb (image_name, content, description) VALUES (?, ?, ?)");
jdbcMessageHandler.setPreparedStatementSetter((ps, m) -> {
ps.setString(1, m.getHeaders().get(FileHeaders.FILENAME));
try (FileInputStream inputStream = new FileInputStream((File) m.getPayload())) {
ps.setBlob(2, inputStream);
}
catch (Exception e) {
throw new MessageHandlingException(m, e);
}
ps.setClob(3, new StringReader(m.getHeaders().get("description", String.class)));
});
return jdbcMessageHandler;
}
----
From the XML configuration perspective, the `prepared-statement-setter` attribute is available on the
`<int-jdbc:outbound-channel-adapter>` component, to specify a `MessagePreparedStatementSetter`
bean reference.
[[jdbc-outbound-gateway]]
=== Outbound Gateway
@@ -239,11 +282,19 @@ If keys-generated="true" then the root of the expression is the generated keys (
The outbound gateway requires a reference to either a DataSource or a JdbcTemplate.
It can also have a `SqlParameterSourceFactory` injected to control the binding of the incoming message to the query.
Starting with the _version 4.2_ the `request-prepared-statement-setter` attribute is available on the
`<int-jdbc:outbound-gateway>` as an alternative to the `request-sql-parameter-source-factory`.
It allows you to specify a `MessagePreparedStatementSetter` bean reference, which implements more sophisticated
`PreparedStatement` preparation before its execution.
See <<jdbc-outbound-channel-adapter>> for more information about `MessagePreparedStatementSetter`.
[[jdbc-message-store]]
=== JDBC Message Store
Spring Integration provides 2 JDBC specifc Message Store implementations.
The first one, is the `JdbcMessageStore` which is suitable to be used in conjunction with _Aggregators_ and the _Claimcheck_ pattern.
Spring Integration provides 2 JDBC specific Message Store implementations.
The first one, is the `JdbcMessageStore` which is suitable to be used in conjunction with _Aggregators_ and the
_Claim-Check_ pattern.
While it can be used for backing _Message Channels_ as well, you may want to consider using the `JdbcChannelMessageStore` implementation instead, as it provides a more targeted and scalable implementation.
[[jdbc-message-store-generic]]

View File

@@ -76,6 +76,14 @@ Codec-based transformers and message converters are also provided.
See <<codec>> for more information.
[[x4.2-prepared-statement-setter]]
==== Message PreparedStatement Setter
A new `MessagePreparedStatementSetter` functional interface callback is available for the `JdbcMessageHandler`
(`<int-jdbc:outbound-gateway>` and `<int-jdbc:outbound-channel-adapter>`) as an alternative to the
`SqlParameterSourceFactory` to populate parameters on the `PreparedStatement` with the `requestMessage` context.
See <<jdbc-outbound-channel-adapter>> for more information.
[[x4.2-general]]
=== General Changes