diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcMessageHandler.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcMessageHandler.java index bc9493caa3..3bb9f5854f 100644 --- a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcMessageHandler.java +++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcMessageHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -19,7 +19,6 @@ package org.springframework.integration.jdbc; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.Statement; import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; @@ -35,7 +34,9 @@ import org.springframework.integration.handler.AbstractMessageHandler; import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.jdbc.core.ColumnMapRowMapper; import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.PreparedStatementCreator; +import org.springframework.jdbc.core.PreparedStatementCreatorFactory; import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.RowMapperResultSetExtractor; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; @@ -44,6 +45,7 @@ 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.lang.Nullable; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; import org.springframework.util.Assert; @@ -84,11 +86,10 @@ public class JdbcMessageHandler extends AbstractMessageHandler { private final NamedParameterJdbcOperations jdbcOperations; - private final PreparedStatementCreator generatedKeysStatementCreator = - con -> con.prepareStatement(JdbcMessageHandler.this.updateSql, Statement.RETURN_GENERATED_KEYS); - private String updateSql; + private PreparedStatementCreator generatedKeysStatementCreator; + private SqlParameterSourceFactory sqlParameterSourceFactory; private boolean keysGenerated; @@ -102,8 +103,7 @@ public class JdbcMessageHandler extends AbstractMessageHandler { * @param updateSql query to execute */ public JdbcMessageHandler(DataSource dataSource, String updateSql) { - this.jdbcOperations = new NamedParameterJdbcTemplate(dataSource); - this.updateSql = updateSql; + this(new JdbcTemplate(dataSource), updateSql); } /** @@ -113,8 +113,10 @@ public class JdbcMessageHandler extends AbstractMessageHandler { * @param updateSql query to execute */ public JdbcMessageHandler(JdbcOperations jdbcOperations, String updateSql) { + Assert.notNull(jdbcOperations, "'jdbcOperations' must not be null."); + Assert.hasText(updateSql, "'updateSql' must not be empty."); this.jdbcOperations = new NamedParameterJdbcTemplate(jdbcOperations); - setUpdateSql(updateSql); + this.updateSql = updateSql; } /** @@ -129,7 +131,9 @@ public class JdbcMessageHandler extends AbstractMessageHandler { /** * Configure an SQL statement to perform an UPDATE on the target database. * @param updateSql the SQL statement to perform. + * @deprecated since 5.1.3 in favor of constructor argument. */ + @Deprecated public final void setUpdateSql(String updateSql) { Assert.hasText(updateSql, "'updateSql' must not be empty."); this.updateSql = updateSql; @@ -146,8 +150,15 @@ public class JdbcMessageHandler extends AbstractMessageHandler { * @param preparedStatementSetter the {@link MessagePreparedStatementSetter} to set. * @since 4.2 */ - public void setPreparedStatementSetter(MessagePreparedStatementSetter preparedStatementSetter) { + public void setPreparedStatementSetter(@Nullable MessagePreparedStatementSetter preparedStatementSetter) { this.preparedStatementSetter = preparedStatementSetter; + if (preparedStatementSetter != null) { + PreparedStatementCreatorFactory preparedStatementCreatorFactory = + new PreparedStatementCreatorFactory(this.updateSql); + preparedStatementCreatorFactory.setReturnGeneratedKeys(true); + this.generatedKeysStatementCreator = + preparedStatementCreatorFactory.newPreparedStatementCreator((Object[]) null); + } } @Override diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcPollingChannelAdapter.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcPollingChannelAdapter.java index 22dfa1d6f2..aa66e4247d 100644 --- a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcPollingChannelAdapter.java +++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcPollingChannelAdapter.java @@ -16,22 +16,29 @@ package org.springframework.integration.jdbc; +import java.sql.Connection; import java.sql.PreparedStatement; +import java.sql.SQLException; import java.util.List; import java.util.function.Consumer; import javax.sql.DataSource; +import org.springframework.beans.factory.BeanFactory; import org.springframework.integration.endpoint.AbstractMessageSource; import org.springframework.jdbc.core.ColumnMapRowMapper; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.ParameterDisposer; import org.springframework.jdbc.core.PreparedStatementCreator; import org.springframework.jdbc.core.PreparedStatementCreatorFactory; +import org.springframework.jdbc.core.PreparedStatementSetter; import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.SqlProvider; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.SqlParameterSource; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -87,24 +94,22 @@ public class JdbcPollingChannelAdapter extends AbstractMessageSource { @Override protected PreparedStatementCreator getPreparedStatementCreator(String sql, - SqlParameterSource paramSource, Consumer customizer) { + SqlParameterSource paramSource, @Nullable Consumer customizer) { PreparedStatementCreator preparedStatementCreator = super.getPreparedStatementCreator(sql, paramSource, customizer); - return con -> { - PreparedStatement preparedStatement = preparedStatementCreator.createPreparedStatement(con); - preparedStatement.setMaxRows(JdbcPollingChannelAdapter.this.maxRows); - return preparedStatement; - }; + return new PreparedStatementCreatorWithMaxRows(preparedStatementCreator, + JdbcPollingChannelAdapter.this.maxRows); } + }; this.selectQuery = selectQuery; this.rowMapper = new ColumnMapRowMapper(); } - public void setRowMapper(RowMapper rowMapper) { + public void setRowMapper(@Nullable RowMapper rowMapper) { this.rowMapper = rowMapper; if (rowMapper == null) { this.rowMapper = new ColumnMapRowMapper(); @@ -120,6 +125,7 @@ public class JdbcPollingChannelAdapter extends AbstractMessageSource { } public void setUpdateSqlParameterSourceFactory(SqlParameterSourceFactory sqlParameterSourceFactory) { + Assert.notNull(sqlParameterSourceFactory, "'sqlParameterSourceFactory' must be null."); this.sqlParameterSourceFactory = sqlParameterSourceFactory; this.sqlParameterSourceFactorySet = true; } @@ -128,7 +134,7 @@ public class JdbcPollingChannelAdapter extends AbstractMessageSource { * A source of parameters for the select query used for polling. * @param sqlQueryParameterSource the sql query parameter source to set */ - public void setSelectSqlParameterSource(SqlParameterSource sqlQueryParameterSource) { + public void setSelectSqlParameterSource(@Nullable SqlParameterSource sqlQueryParameterSource) { this.sqlQueryParameterSource = sqlQueryParameterSource; } @@ -155,9 +161,10 @@ public class JdbcPollingChannelAdapter extends AbstractMessageSource { @Override protected void onInit() { - if (!this.sqlParameterSourceFactorySet && getBeanFactory() != null) { + BeanFactory beanFactory = getBeanFactory(); + if (!this.sqlParameterSourceFactorySet && beanFactory != null) { ((ExpressionEvaluatingSqlParameterSourceFactory) this.sqlParameterSourceFactory) - .setBeanFactory(getBeanFactory()); + .setBeanFactory(beanFactory); } } @@ -190,7 +197,7 @@ public class JdbcPollingChannelAdapter extends AbstractMessageSource { return payload; } - protected List doPoll(SqlParameterSource sqlQueryParameterSource) { + protected List doPoll(@Nullable SqlParameterSource sqlQueryParameterSource) { if (sqlQueryParameterSource != null) { return this.jdbcOperations.query(this.selectQuery, sqlQueryParameterSource, this.rowMapper); } @@ -200,8 +207,52 @@ public class JdbcPollingChannelAdapter extends AbstractMessageSource { } private void executeUpdateQuery(Object obj) { - SqlParameterSource updateParameterSource = this.sqlParameterSourceFactory.createParameterSource(obj); - this.jdbcOperations.update(this.updateSql, updateParameterSource); + this.jdbcOperations.update(this.updateSql, this.sqlParameterSourceFactory.createParameterSource(obj)); + } + + private static final class PreparedStatementCreatorWithMaxRows + implements PreparedStatementCreator, PreparedStatementSetter, SqlProvider, ParameterDisposer { + + private final PreparedStatementCreator delegate; + + private final int maxRows; + + private PreparedStatementCreatorWithMaxRows(PreparedStatementCreator delegate, int maxRows) { + this.delegate = delegate; + this.maxRows = maxRows; + } + + @Override + public PreparedStatement createPreparedStatement(Connection con) throws SQLException { + PreparedStatement preparedStatement = this.delegate.createPreparedStatement(con); + preparedStatement.setMaxRows(this.maxRows); // We can't mutate provided JdbOperations for this option + return preparedStatement; + } + + @Override + public String getSql() { + if (this.delegate instanceof SqlProvider) { + return ((SqlProvider) this.delegate).getSql(); + } + else { + return null; + } + } + + @Override + public void setValues(PreparedStatement ps) throws SQLException { + if (this.delegate instanceof PreparedStatementSetter) { + ((PreparedStatementSetter) this.delegate).setValues(ps); + } + } + + @Override + public void cleanupParameters() { + if (this.delegate instanceof ParameterDisposer) { + ((ParameterDisposer) this.delegate).cleanupParameters(); + } + } + } } 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 f5bd9ccbc2..81817c640e 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import java.sql.PreparedStatement; import java.sql.SQLException; @@ -28,6 +31,7 @@ import java.util.Map; import javax.sql.DataSource; +import org.apache.commons.logging.Log; import org.junit.After; import org.junit.Assert; import org.junit.Test; @@ -97,6 +101,7 @@ public class JdbcOutboundGatewayParserTests { @SuppressWarnings("unchecked") public void testKeyGeneration() { setUp("handlingKeyGenerationJdbcOutboundGatewayTest.xml", getClass()); + Message message = MessageBuilder.withPayload(Collections.singletonMap("foo", "bar")).build(); this.channel.send(message); @@ -114,8 +119,19 @@ public class JdbcOutboundGatewayParserTests { this.jdbcTemplate.execute("DELETE FROM BARS"); + Object insertGateway = this.context.getBean("insertGatewayWithSetter.handler"); + JdbcTemplate handlerJdbcTemplate = + TestUtils.getPropertyValue(insertGateway, + "handler.jdbcOperations.classicJdbcTemplate", JdbcTemplate.class); + + Log logger = spy(TestUtils.getPropertyValue(handlerJdbcTemplate, "logger", Log.class)); + + given(logger.isDebugEnabled()).willReturn(true); + + new DirectFieldAccessor(handlerJdbcTemplate).setPropertyValue("logger", logger); + MessageChannel setterRequest = this.context.getBean("setterRequest", MessageChannel.class); - setterRequest.send(new GenericMessage("bar2")); + setterRequest.send(new GenericMessage<>("bar2")); reply = this.messagingTemplate.receive(); assertNotNull(reply); @@ -125,6 +141,8 @@ public class JdbcOutboundGatewayParserTests { map = this.jdbcTemplate.queryForMap("SELECT * from BARS"); assertEquals("Wrong id", id, map.get("ID")); assertEquals("Wrong name", "bar2", map.get("name")); + + verify(logger).debug("Executing prepared SQL statement [insert into bars (status, name) values (0, ?)]"); } @Test @@ -142,8 +160,21 @@ public class JdbcOutboundGatewayParserTests { } @Test - public void testWithPoller() throws Exception { + public void testWithPoller() { setUp("JdbcOutboundGatewayWithPollerTest-context.xml", this.getClass()); + + Object insertGateway = this.context.getBean("jdbcOutboundGateway.handler"); + JdbcTemplate pollerJdbcTemplate = + TestUtils.getPropertyValue(insertGateway, + "poller.jdbcOperations.classicJdbcTemplate", JdbcTemplate.class); + + Log logger = spy(TestUtils.getPropertyValue(pollerJdbcTemplate, "logger", Log.class)); + + given(logger.isDebugEnabled()).willReturn(true); + + new DirectFieldAccessor(pollerJdbcTemplate).setPropertyValue("logger", logger); + + Message message = MessageBuilder.withPayload(Collections.singletonMap("foo", "bar")).build(); this.channel.send(message); @@ -157,10 +188,12 @@ public class JdbcOutboundGatewayParserTests { Map map = this.jdbcTemplate.queryForMap("SELECT * from BAZZ"); assertEquals("Wrong id", message.getHeaders().getId().toString(), map.get("ID")); assertEquals("Wrong name", "bar", map.get("name")); + + verify(logger).debug("Executing prepared SQL statement [select * from bazz where id=?]"); } @Test - public void testWithSelectQueryOnly() throws Exception { + public void testWithSelectQueryOnly() { setUp("JdbcOutboundGatewayWithSelectTest-context.xml", getClass()); Message message = MessageBuilder.withPayload(100).build(); diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/handlingKeyGenerationJdbcOutboundGatewayTest.xml b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/handlingKeyGenerationJdbcOutboundGatewayTest.xml index 0efa5336a8..cdc3438360 100644 --- a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/handlingKeyGenerationJdbcOutboundGatewayTest.xml +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/handlingKeyGenerationJdbcOutboundGatewayTest.xml @@ -20,7 +20,8 @@ -