Skip to content

Commit 7dfc1ef

Browse files
committed
spring-projectsGH-2727: Ensure JDBC queries are logged
Fixes: spring-projects#2727 The `JdbcTemplate` logs a message for the sql to execute when it is supplied by the `QueryProvider`. * Refactor `JdbcPollingChannelAdapter` to use new introduced internal `PreparedStatementCreatorWithMaxRows` with the `QueryProvider` * Refactor `JdbcMessageHandler` to instantiate a `generatedKeysStatementCreator` based on the `PreparedStatementCreatorFactory` * Verify in the `JdbcOutboundGatewayParserTests` that both fixes logs sql queries properly
1 parent f82c716 commit 7dfc1ef

File tree

4 files changed

+101
-27
lines changed

4 files changed

+101
-27
lines changed

spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcMessageHandler.java

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,7 +19,6 @@
1919
import java.sql.PreparedStatement;
2020
import java.sql.ResultSet;
2121
import java.sql.SQLException;
22-
import java.sql.Statement;
2322
import java.util.Arrays;
2423
import java.util.Collections;
2524
import java.util.LinkedList;
@@ -35,7 +34,9 @@
3534
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
3635
import org.springframework.jdbc.core.ColumnMapRowMapper;
3736
import org.springframework.jdbc.core.JdbcOperations;
37+
import org.springframework.jdbc.core.JdbcTemplate;
3838
import org.springframework.jdbc.core.PreparedStatementCreator;
39+
import org.springframework.jdbc.core.PreparedStatementCreatorFactory;
3940
import org.springframework.jdbc.core.ResultSetExtractor;
4041
import org.springframework.jdbc.core.RowMapperResultSetExtractor;
4142
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
@@ -44,6 +45,7 @@
4445
import org.springframework.jdbc.support.GeneratedKeyHolder;
4546
import org.springframework.jdbc.support.JdbcUtils;
4647
import org.springframework.jdbc.support.KeyHolder;
48+
import org.springframework.lang.Nullable;
4749
import org.springframework.messaging.Message;
4850
import org.springframework.messaging.MessageHeaders;
4951
import org.springframework.util.Assert;
@@ -84,11 +86,10 @@ public class JdbcMessageHandler extends AbstractMessageHandler {
8486

8587
private final NamedParameterJdbcOperations jdbcOperations;
8688

87-
private final PreparedStatementCreator generatedKeysStatementCreator =
88-
con -> con.prepareStatement(JdbcMessageHandler.this.updateSql, Statement.RETURN_GENERATED_KEYS);
89-
9089
private String updateSql;
9190

91+
private PreparedStatementCreator generatedKeysStatementCreator;
92+
9293
private SqlParameterSourceFactory sqlParameterSourceFactory;
9394

9495
private boolean keysGenerated;
@@ -102,8 +103,7 @@ public class JdbcMessageHandler extends AbstractMessageHandler {
102103
* @param updateSql query to execute
103104
*/
104105
public JdbcMessageHandler(DataSource dataSource, String updateSql) {
105-
this.jdbcOperations = new NamedParameterJdbcTemplate(dataSource);
106-
this.updateSql = updateSql;
106+
this(new JdbcTemplate(dataSource), updateSql);
107107
}
108108

109109
/**
@@ -113,8 +113,10 @@ public JdbcMessageHandler(DataSource dataSource, String updateSql) {
113113
* @param updateSql query to execute
114114
*/
115115
public JdbcMessageHandler(JdbcOperations jdbcOperations, String updateSql) {
116+
Assert.notNull(jdbcOperations, "'jdbcOperations' must not be null.");
117+
Assert.hasText(updateSql, "'updateSql' must not be empty.");
116118
this.jdbcOperations = new NamedParameterJdbcTemplate(jdbcOperations);
117-
setUpdateSql(updateSql);
119+
this.updateSql = updateSql;
118120
}
119121

120122
/**
@@ -129,7 +131,9 @@ public void setKeysGenerated(boolean keysGenerated) {
129131
/**
130132
* Configure an SQL statement to perform an UPDATE on the target database.
131133
* @param updateSql the SQL statement to perform.
134+
* @deprecated since 5.1.3 in favor of constructor argument.
132135
*/
136+
@Deprecated
133137
public final void setUpdateSql(String updateSql) {
134138
Assert.hasText(updateSql, "'updateSql' must not be empty.");
135139
this.updateSql = updateSql;
@@ -146,8 +150,15 @@ public void setSqlParameterSourceFactory(SqlParameterSourceFactory sqlParameterS
146150
* @param preparedStatementSetter the {@link MessagePreparedStatementSetter} to set.
147151
* @since 4.2
148152
*/
149-
public void setPreparedStatementSetter(MessagePreparedStatementSetter preparedStatementSetter) {
153+
public void setPreparedStatementSetter(@Nullable MessagePreparedStatementSetter preparedStatementSetter) {
150154
this.preparedStatementSetter = preparedStatementSetter;
155+
if (preparedStatementSetter != null) {
156+
PreparedStatementCreatorFactory preparedStatementCreatorFactory =
157+
new PreparedStatementCreatorFactory(this.updateSql);
158+
preparedStatementCreatorFactory.setReturnGeneratedKeys(true);
159+
this.generatedKeysStatementCreator =
160+
preparedStatementCreatorFactory.newPreparedStatementCreator((Object[]) null);
161+
}
151162
}
152163

153164
@Override

spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcPollingChannelAdapter.java

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,27 @@
1616

1717
package org.springframework.integration.jdbc;
1818

19+
import java.sql.Connection;
1920
import java.sql.PreparedStatement;
21+
import java.sql.SQLException;
2022
import java.util.List;
2123
import java.util.function.Consumer;
2224

2325
import javax.sql.DataSource;
2426

27+
import org.springframework.beans.factory.BeanFactory;
2528
import org.springframework.integration.endpoint.AbstractMessageSource;
2629
import org.springframework.jdbc.core.ColumnMapRowMapper;
2730
import org.springframework.jdbc.core.JdbcOperations;
2831
import org.springframework.jdbc.core.JdbcTemplate;
2932
import org.springframework.jdbc.core.PreparedStatementCreator;
3033
import org.springframework.jdbc.core.PreparedStatementCreatorFactory;
3134
import org.springframework.jdbc.core.RowMapper;
35+
import org.springframework.jdbc.core.SqlProvider;
3236
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
3337
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
3438
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
39+
import org.springframework.lang.Nullable;
3540
import org.springframework.util.Assert;
3641

3742
/**
@@ -87,24 +92,22 @@ public JdbcPollingChannelAdapter(JdbcOperations jdbcOperations, String selectQue
8792

8893
@Override
8994
protected PreparedStatementCreator getPreparedStatementCreator(String sql,
90-
SqlParameterSource paramSource, Consumer<PreparedStatementCreatorFactory> customizer) {
95+
SqlParameterSource paramSource, @Nullable Consumer<PreparedStatementCreatorFactory> customizer) {
9196

9297
PreparedStatementCreator preparedStatementCreator =
9398
super.getPreparedStatementCreator(sql, paramSource, customizer);
9499

95-
return con -> {
96-
PreparedStatement preparedStatement = preparedStatementCreator.createPreparedStatement(con);
97-
preparedStatement.setMaxRows(JdbcPollingChannelAdapter.this.maxRows);
98-
return preparedStatement;
99-
};
100+
return new PreparedStatementCreatorWithMaxRows(preparedStatementCreator,
101+
JdbcPollingChannelAdapter.this.maxRows);
100102
}
103+
101104
};
102105

103106
this.selectQuery = selectQuery;
104107
this.rowMapper = new ColumnMapRowMapper();
105108
}
106109

107-
public void setRowMapper(RowMapper<?> rowMapper) {
110+
public void setRowMapper(@Nullable RowMapper<?> rowMapper) {
108111
this.rowMapper = rowMapper;
109112
if (rowMapper == null) {
110113
this.rowMapper = new ColumnMapRowMapper();
@@ -120,6 +123,7 @@ public void setUpdatePerRow(boolean updatePerRow) {
120123
}
121124

122125
public void setUpdateSqlParameterSourceFactory(SqlParameterSourceFactory sqlParameterSourceFactory) {
126+
Assert.notNull(sqlParameterSourceFactory, "'sqlParameterSourceFactory' must be null.");
123127
this.sqlParameterSourceFactory = sqlParameterSourceFactory;
124128
this.sqlParameterSourceFactorySet = true;
125129
}
@@ -128,7 +132,7 @@ public void setUpdateSqlParameterSourceFactory(SqlParameterSourceFactory sqlPara
128132
* A source of parameters for the select query used for polling.
129133
* @param sqlQueryParameterSource the sql query parameter source to set
130134
*/
131-
public void setSelectSqlParameterSource(SqlParameterSource sqlQueryParameterSource) {
135+
public void setSelectSqlParameterSource(@Nullable SqlParameterSource sqlQueryParameterSource) {
132136
this.sqlQueryParameterSource = sqlQueryParameterSource;
133137
}
134138

@@ -155,9 +159,10 @@ public void setMaxRows(int maxRows) {
155159

156160
@Override
157161
protected void onInit() {
158-
if (!this.sqlParameterSourceFactorySet && getBeanFactory() != null) {
162+
BeanFactory beanFactory = getBeanFactory();
163+
if (!this.sqlParameterSourceFactorySet && beanFactory != null) {
159164
((ExpressionEvaluatingSqlParameterSourceFactory) this.sqlParameterSourceFactory)
160-
.setBeanFactory(getBeanFactory());
165+
.setBeanFactory(beanFactory);
161166
}
162167
}
163168

@@ -190,7 +195,7 @@ protected Object doReceive() {
190195
return payload;
191196
}
192197

193-
protected List<?> doPoll(SqlParameterSource sqlQueryParameterSource) {
198+
protected List<?> doPoll(@Nullable SqlParameterSource sqlQueryParameterSource) {
194199
if (sqlQueryParameterSource != null) {
195200
return this.jdbcOperations.query(this.selectQuery, sqlQueryParameterSource, this.rowMapper);
196201
}
@@ -200,8 +205,32 @@ protected List<?> doPoll(SqlParameterSource sqlQueryParameterSource) {
200205
}
201206

202207
private void executeUpdateQuery(Object obj) {
203-
SqlParameterSource updateParameterSource = this.sqlParameterSourceFactory.createParameterSource(obj);
204-
this.jdbcOperations.update(this.updateSql, updateParameterSource);
208+
this.jdbcOperations.update(this.updateSql, this.sqlParameterSourceFactory.createParameterSource(obj));
209+
}
210+
211+
private static final class PreparedStatementCreatorWithMaxRows implements PreparedStatementCreator, SqlProvider {
212+
213+
private final PreparedStatementCreator delegate;
214+
215+
private final int maxRows;
216+
217+
private PreparedStatementCreatorWithMaxRows(PreparedStatementCreator delegate, int maxRows) {
218+
this.delegate = delegate;
219+
this.maxRows = maxRows;
220+
}
221+
222+
@Override
223+
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
224+
PreparedStatement preparedStatement = this.delegate.createPreparedStatement(con);
225+
preparedStatement.setMaxRows(this.maxRows); // We can't mutate provided JdbOperations for this option
226+
return preparedStatement;
227+
}
228+
229+
@Override
230+
public String getSql() {
231+
return ((SqlProvider) this.delegate).getSql();
232+
}
233+
205234
}
206235

207236
}

spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/JdbcOutboundGatewayParserTests.java

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,6 +20,9 @@
2020
import static org.junit.Assert.assertFalse;
2121
import static org.junit.Assert.assertNotNull;
2222
import static org.junit.Assert.assertTrue;
23+
import static org.mockito.BDDMockito.given;
24+
import static org.mockito.Mockito.spy;
25+
import static org.mockito.Mockito.verify;
2326

2427
import java.sql.PreparedStatement;
2528
import java.sql.SQLException;
@@ -28,6 +31,7 @@
2831

2932
import javax.sql.DataSource;
3033

34+
import org.apache.commons.logging.Log;
3135
import org.junit.After;
3236
import org.junit.Assert;
3337
import org.junit.Test;
@@ -97,6 +101,7 @@ public void testMapPayloadMapReply() {
97101
@SuppressWarnings("unchecked")
98102
public void testKeyGeneration() {
99103
setUp("handlingKeyGenerationJdbcOutboundGatewayTest.xml", getClass());
104+
100105
Message<?> message = MessageBuilder.withPayload(Collections.singletonMap("foo", "bar")).build();
101106

102107
this.channel.send(message);
@@ -114,8 +119,19 @@ public void testKeyGeneration() {
114119

115120
this.jdbcTemplate.execute("DELETE FROM BARS");
116121

122+
Object insertGateway = this.context.getBean("insertGatewayWithSetter.handler");
123+
JdbcTemplate handlerJdbcTemplate =
124+
TestUtils.getPropertyValue(insertGateway,
125+
"handler.jdbcOperations.classicJdbcTemplate", JdbcTemplate.class);
126+
127+
Log logger = spy(TestUtils.getPropertyValue(handlerJdbcTemplate, "logger", Log.class));
128+
129+
given(logger.isDebugEnabled()).willReturn(true);
130+
131+
new DirectFieldAccessor(handlerJdbcTemplate).setPropertyValue("logger", logger);
132+
117133
MessageChannel setterRequest = this.context.getBean("setterRequest", MessageChannel.class);
118-
setterRequest.send(new GenericMessage<String>("bar2"));
134+
setterRequest.send(new GenericMessage<>("bar2"));
119135
reply = this.messagingTemplate.receive();
120136
assertNotNull(reply);
121137

@@ -125,6 +141,8 @@ public void testKeyGeneration() {
125141
map = this.jdbcTemplate.queryForMap("SELECT * from BARS");
126142
assertEquals("Wrong id", id, map.get("ID"));
127143
assertEquals("Wrong name", "bar2", map.get("name"));
144+
145+
verify(logger).debug("Executing prepared SQL statement [insert into bars (status, name) values (0, ?)]");
128146
}
129147

130148
@Test
@@ -142,8 +160,21 @@ public void testCountUpdates() {
142160
}
143161

144162
@Test
145-
public void testWithPoller() throws Exception {
163+
public void testWithPoller() {
146164
setUp("JdbcOutboundGatewayWithPollerTest-context.xml", this.getClass());
165+
166+
Object insertGateway = this.context.getBean("jdbcOutboundGateway.handler");
167+
JdbcTemplate pollerJdbcTemplate =
168+
TestUtils.getPropertyValue(insertGateway,
169+
"poller.jdbcOperations.classicJdbcTemplate", JdbcTemplate.class);
170+
171+
Log logger = spy(TestUtils.getPropertyValue(pollerJdbcTemplate, "logger", Log.class));
172+
173+
given(logger.isDebugEnabled()).willReturn(true);
174+
175+
new DirectFieldAccessor(pollerJdbcTemplate).setPropertyValue("logger", logger);
176+
177+
147178
Message<?> message = MessageBuilder.withPayload(Collections.singletonMap("foo", "bar")).build();
148179

149180
this.channel.send(message);
@@ -157,10 +188,12 @@ public void testWithPoller() throws Exception {
157188
Map<String, Object> map = this.jdbcTemplate.queryForMap("SELECT * from BAZZ");
158189
assertEquals("Wrong id", message.getHeaders().getId().toString(), map.get("ID"));
159190
assertEquals("Wrong name", "bar", map.get("name"));
191+
192+
verify(logger).debug("Executing prepared SQL statement [select * from bazz where id=?]");
160193
}
161194

162195
@Test
163-
public void testWithSelectQueryOnly() throws Exception {
196+
public void testWithSelectQueryOnly() {
164197
setUp("JdbcOutboundGatewayWithSelectTest-context.xml", getClass());
165198
Message<?> message = MessageBuilder.withPayload(100).build();
166199

spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/handlingKeyGenerationJdbcOutboundGatewayTest.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
<beans:bean id="messagePreparedStatementSetter"
2121
class="org.springframework.integration.jdbc.config.JdbcOutboundGatewayParserTests$TestMessagePreparedStatementSetter"/>
2222

23-
<outbound-gateway update="insert into bars (status, name) values (0, ?)"
23+
<outbound-gateway id="insertGatewayWithSetter"
24+
update="insert into bars (status, name) values (0, ?)"
2425
request-channel="setterRequest"
2526
reply-channel="output"
2627
data-source="dataSource"

0 commit comments

Comments
 (0)