diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcOutboundGateway.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcOutboundGateway.java index c45569c0da..53474cbfdb 100644 --- a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcOutboundGateway.java +++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcOutboundGateway.java @@ -15,6 +15,7 @@ */ package org.springframework.integration.jdbc; +import java.util.Collections; import java.util.List; import javax.sql.DataSource; @@ -28,6 +29,7 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.SqlParameterSource; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * @author Dave Syer @@ -60,14 +62,28 @@ public class JdbcOutboundGateway extends AbstractReplyProducingMessageHandler im } public JdbcOutboundGateway(JdbcOperations jdbcOperations, String updateQuery, String selectQuery) { - if (selectQuery != null) { + + Assert.notNull(jdbcOperations, "'jdbcOperations' must not be null."); + + if (!StringUtils.hasText(updateQuery) && !StringUtils.hasText(selectQuery)) { + throw new IllegalArgumentException("The 'updateQuery' and the 'selectQuery' must not both be null or empty."); + } + + if (StringUtils.hasText(selectQuery)) { poller = new JdbcPollingChannelAdapter(jdbcOperations, selectQuery); poller.setMaxRowsPerPoll(1); } else { poller = null; } - handler = new JdbcMessageHandler(jdbcOperations, updateQuery); + + if (StringUtils.hasText(updateQuery)) { + handler = new JdbcMessageHandler(jdbcOperations, updateQuery); + } + else { + handler = null; + } + } /** @@ -96,13 +112,24 @@ public class JdbcOutboundGateway extends AbstractReplyProducingMessageHandler im poller.setMaxRowsPerPoll(this.maxRowsPerPoll); } - handler.afterPropertiesSet(); + if (this.handler!= null) { + handler.afterPropertiesSet(); + } } @Override protected Object handleRequestMessage(Message requestMessage) { - List list = handler.executeUpdateQuery(requestMessage, keysGenerated); + + List list; + + if (this.handler != null) { + list = handler.executeUpdateQuery(requestMessage, keysGenerated); + } + else { + list = Collections.emptyList(); + } + if (poller != null) { SqlParameterSource sqlQueryParameterSource = sqlParameterSourceFactory .createParameterSource(requestMessage); diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/config/JdbcOutboundGatewayParser.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/config/JdbcOutboundGatewayParser.java index 1c7249ad20..864b264959 100644 --- a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/config/JdbcOutboundGatewayParser.java +++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/config/JdbcOutboundGatewayParser.java @@ -36,22 +36,18 @@ public class JdbcOutboundGatewayParser extends AbstractConsumerEndpointParser { String jdbcOperationsRef = element.getAttribute("jdbc-operations"); boolean refToDataSourceSet = StringUtils.hasText(dataSourceRef); boolean refToJdbcOperationsSet = StringUtils.hasText(jdbcOperationsRef); + if ((refToDataSourceSet && refToJdbcOperationsSet) || (!refToDataSourceSet && !refToJdbcOperationsSet)) { parserContext.getReaderContext().error( "Exactly one of the attributes data-source or " + "simple-jdbc-operations should be set for the JDBC outbound-gateway", element); } + String selectQuery = IntegrationNamespaceUtils.getTextFromAttributeOrNestedElement(element, "query", parserContext); - if (!StringUtils.hasText(selectQuery)) { - selectQuery = null; - } String updateQuery = IntegrationNamespaceUtils.getTextFromAttributeOrNestedElement(element, "update", parserContext); - if (!StringUtils.hasText(updateQuery)) { - parserContext.getReaderContext().error("The update attribute is required", element); - return null; - } + BeanDefinitionBuilder builder = BeanDefinitionBuilder .genericBeanDefinition(JdbcOutboundGateway.class); if (refToDataSourceSet) { @@ -61,8 +57,8 @@ public class JdbcOutboundGatewayParser extends AbstractConsumerEndpointParser { builder.addConstructorArgReference(jdbcOperationsRef); } - builder.getRawBeanDefinition().getConstructorArgumentValues().addIndexedArgumentValue(1, updateQuery); - builder.getRawBeanDefinition().getConstructorArgumentValues().addIndexedArgumentValue(2, selectQuery); + builder.addConstructorArgValue(updateQuery); + builder.addConstructorArgValue(selectQuery); IntegrationNamespaceUtils .setReferenceIfAttributeDefined(builder, element, "reply-sql-parameter-source-factory"); diff --git a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/config/spring-integration-jdbc-2.2.xsd b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/config/spring-integration-jdbc-2.2.xsd index 1217053ebb..ae8c3bed9a 100644 --- a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/config/spring-integration-jdbc-2.2.xsd +++ b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/config/spring-integration-jdbc-2.2.xsd @@ -315,9 +315,14 @@ Defines an outbound Channel Gateway for updating a - database in response to a message on the request - channel and getting a response - on the reply channel. The response can be created from a query + database in response to a message on the request channel, and/or + for retrieving data from the database using the input message as + a source of parameters for the specified SQL select query. + + The database response will be used to create the response Message + on the reply channel. + + The response can be created from a query supplied here, or (if keys-generated="true") can be the primary keys generated from an auto-increment, or else just a count of the number of rows affected by the update. The response @@ -334,9 +339,19 @@ - An update query to execute when a message is + An update query to be executed when a message is received. If this is in a transaction then the update will roll back when the transaction does. + + The update can also be specified using the + "update" attribute. + + Since Spring Integration 2.2 specifying an + update query is optional, if at least the + select query is specified. + + If you specify both, update- and select + query, then the update is executed first. @@ -353,6 +368,13 @@ update will roll back when the transaction does. The update can also be specified as a nested element. + + Since Spring Integration 2.2 specifying an + update query is optional, if at least the + select query is specified. + + If you specify both, update- and select + query, then the update is executed first. diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcOutboundGatewayTests.java b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcOutboundGatewayTests.java index 1bfe6658cf..e066192c6c 100644 --- a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcOutboundGatewayTests.java +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcOutboundGatewayTests.java @@ -16,9 +16,12 @@ import static org.junit.Assert.fail; import javax.sql.DataSource; +import junit.framework.Assert; + import static junit.framework.Assert.assertEquals; import org.junit.Test; +import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; /** @@ -35,12 +38,12 @@ public class JdbcOutboundGatewayTests { DataSource dataSource = new EmbeddedDatabaseBuilder().build(); - JdbcOutboundGateway jdbcOutboundGateway = new JdbcOutboundGateway(dataSource, "select * from DOES_NOT_EXIST"); + JdbcOutboundGateway jdbcOutboundGateway = new JdbcOutboundGateway(dataSource, "update something"); try { jdbcOutboundGateway.setMaxRowsPerPoll(10); jdbcOutboundGateway.onInit(); - + } catch (IllegalArgumentException e) { assertEquals("If you want to set 'maxRowsPerPoll', then you must provide a 'selectQuery'.", e.getMessage()); return; @@ -50,6 +53,41 @@ public class JdbcOutboundGatewayTests { } + @Test + public void testConstructorWithNulljdbcOperations() { + + JdbcOperations jdbcOperations = null; + + try { + new JdbcOutboundGateway(jdbcOperations, "select * from DOES_NOT_EXIST"); + } + catch (IllegalArgumentException e) { + Assert.assertEquals("'jdbcOperations' must not be null.", e.getMessage()); + return; + } + + fail("Expected an IllegalArgumentException to be thrown."); + } + + @Test + public void testConstructorWithEmptyAndNullQueries() { + + final DataSource dataSource = new EmbeddedDatabaseBuilder().build(); + + final String selectQuery = " "; + final String updateQuery = null; + + try { + new JdbcOutboundGateway(dataSource, updateQuery, selectQuery); + } + catch (IllegalArgumentException e) { + Assert.assertEquals("The 'updateQuery' and the 'selectQuery' must not both be null or empty.", e.getMessage()); + return; + } + + fail("Expected an IllegalArgumentException to be thrown."); + } + /** * Test method for * {@link org.springframework.integration.jdbc.JdbcOutboundGateway#setMaxRowsPerPoll(Integer)}. diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/JdbcOutboundGatewayParserTests.java b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/JdbcOutboundGatewayParserTests.java index 78f632b0a4..75b4f0a40f 100644 --- a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/JdbcOutboundGatewayParserTests.java +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/JdbcOutboundGatewayParserTests.java @@ -119,6 +119,28 @@ public class JdbcOutboundGatewayParserTests { assertEquals("bar", payload.get("name")); } + @Test + public void testWithSelectQueryOnly() throws Exception{ + ApplicationContext ac = new ClassPathXmlApplicationContext("JdbcOutboundGatewayWithSelectTest-context.xml", this.getClass()); + Message message = MessageBuilder.withPayload(Integer.valueOf(100)).build(); + MessageChannel requestChannel = ac.getBean("request", MessageChannel.class); + PollableChannel replyChannel = ac.getBean("reply", PollableChannel.class); + + requestChannel.send(message); + Thread.sleep(1000); + + @SuppressWarnings("unchecked") + Message> reply = (Message>) replyChannel.receive(500); + + String id = (String) reply.getPayload().get("id"); + Integer status = (Integer) reply.getPayload().get("status"); + String name = (String) reply.getPayload().get("name"); + + assertEquals("100", id); + assertEquals(Integer.valueOf(3), status); + assertEquals("Cartman", name); + } + @Test public void testReplyTimeoutIsSet() throws Exception { setUp("JdbcOutboundGatewayWithPollerTest-context.xml", getClass()); diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/JdbcOutboundGatewayWithSelectTest-context.xml b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/JdbcOutboundGatewayWithSelectTest-context.xml new file mode 100644 index 0000000000..462d784978 --- /dev/null +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/JdbcOutboundGatewayWithSelectTest-context.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/outboundPollerSchemaWithData.sql b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/outboundPollerSchemaWithData.sql new file mode 100644 index 0000000000..f07a6ca8f4 --- /dev/null +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/outboundPollerSchemaWithData.sql @@ -0,0 +1,5 @@ +drop table bazz; + +create table bazz(id varchar(100),status int,name varchar(20)); + +INSERT INTO bazz (id, status, name) VALUES (100, 3, 'Cartman') \ No newline at end of file diff --git a/src/reference/docbook/jdbc.xml b/src/reference/docbook/jdbc.xml index 85491351bc..9927428aed 100644 --- a/src/reference/docbook/jdbc.xml +++ b/src/reference/docbook/jdbc.xml @@ -195,9 +195,10 @@ SQL query and then respond with the result sending it to a reply channel. The message payload and headers are available by default as input parameters to the query, for instance: + ]]> + request-channel="input" reply-channel="output" data-source="dataSource" />]]> The result of the above would be to insert a record into the "foos" table and return a message to the output channel indicating the number of @@ -223,6 +224,21 @@ query="select * from foos where id=:headers[$id]" request-channel="input" reply-channel="output" data-source="dataSource"/>]]> + + Since Spring Integration 2.2 the update SQL query is + no longer mandatory. You can now solely provide a select query, using + either the query attribute or + the query sub-element. This is extremely useful if you + need to actively retrieve data using e.g. a generic Gateway or a + Payload Enricher. The reply message is then generated from the result, like + the inbound adapter, and passed to the reply channel. + + ]]> + As with the channel adapters, there is also the option to provide SqlParameterSourceFactory instances for request and reply. The default is the same as for the outbound adapter, so the request diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index 0063ad3d29..767eac8523 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -57,6 +57,14 @@ JdbcCallOperations Cache Statistics +
+ JDBC Adapter - Outbound Gateway + + When using the JDBC Outbound Gateway, the update query is no longer + mandatory. You can now provide solely a select query using the request + message as a source of parameters. + +
Transaction Synchronization