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:
committed by
Gary Russell
parent
057c004e6c
commit
9ff5ab789a
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]]
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user