Skip to content

Commit a3d763d

Browse files
committed
Consistent parameter resolution for batch updates (IN clauses etc)
Includes deprecation of (NamedParameter)BatchUpdateUtils in favor of inlined code in (NamedParameter)JdbcTemplate itself. Issue: SPR-17402
1 parent 2a5d769 commit a3d763d

File tree

8 files changed

+138
-15
lines changed

8 files changed

+138
-15
lines changed

spring-jdbc/src/main/java/org/springframework/jdbc/core/BatchUpdateUtils.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
2929
* @author Thomas Risberg
3030
* @author Juergen Hoeller
3131
* @since 3.0
32+
* @deprecated as of 5.1.3, not used by {@link JdbcTemplate} anymore
3233
*/
34+
@Deprecated
3335
public abstract class BatchUpdateUtils {
3436

3537
public static int[] executeBatchUpdate(

spring-jdbc/src/main/java/org/springframework/jdbc/core/CallableStatementCreatorFactory.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,14 @@ public CallableStatementCreatorFactory(String callString, List<SqlParameter> dec
7171
}
7272

7373

74+
/**
75+
* Return the SQL call string.
76+
* @since 5.1.3
77+
*/
78+
public final String getCallString() {
79+
return this.callString;
80+
}
81+
7482
/**
7583
* Add a new declared parameter.
7684
* <p>Order of parameter addition is significant.

spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -933,6 +933,7 @@ <T>List<T> queryForList(String sql, Object[] args, int[] argTypes, Class<T> elem
933933
* @param pss the ParameterizedPreparedStatementSetter to use
934934
* @return an array containing for each batch another array containing the numbers of rows affected
935935
* by each update in the batch
936+
* @since 3.1
936937
*/
937938
<T> int[][] batchUpdate(String sql, Collection<T> batchArgs, int batchSize,
938939
ParameterizedPreparedStatementSetter<T> pss) throws DataAccessException;

spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -982,8 +982,41 @@ public int[] batchUpdate(String sql, List<Object[]> batchArgs) throws DataAccess
982982
}
983983

984984
@Override
985-
public int[] batchUpdate(String sql, List<Object[]> batchArgs, int[] argTypes) throws DataAccessException {
986-
return BatchUpdateUtils.executeBatchUpdate(sql, batchArgs, argTypes, this);
985+
public int[] batchUpdate(String sql, List<Object[]> batchArgs, final int[] argTypes) throws DataAccessException {
986+
if (batchArgs.isEmpty()) {
987+
return new int[0];
988+
}
989+
990+
return batchUpdate(
991+
sql,
992+
new BatchPreparedStatementSetter() {
993+
@Override
994+
public void setValues(PreparedStatement ps, int i) throws SQLException {
995+
Object[] values = batchArgs.get(i);
996+
int colIndex = 0;
997+
for (Object value : values) {
998+
colIndex++;
999+
if (value instanceof SqlParameterValue) {
1000+
SqlParameterValue paramValue = (SqlParameterValue) value;
1001+
StatementCreatorUtils.setParameterValue(ps, colIndex, paramValue, paramValue.getValue());
1002+
}
1003+
else {
1004+
int colType;
1005+
if (argTypes.length < colIndex) {
1006+
colType = SqlTypeValue.TYPE_UNKNOWN;
1007+
}
1008+
else {
1009+
colType = argTypes[colIndex - 1];
1010+
}
1011+
StatementCreatorUtils.setParameterValue(ps, colIndex, colType, value);
1012+
}
1013+
}
1014+
}
1015+
@Override
1016+
public int getBatchSize() {
1017+
return batchArgs.size();
1018+
}
1019+
});
9871020
}
9881021

9891022
@Override
@@ -996,11 +1029,7 @@ public <T> int[][] batchUpdate(String sql, final Collection<T> batchArgs, final
9961029
int[][] result = execute(sql, (PreparedStatementCallback<int[][]>) ps -> {
9971030
List<int[]> rowsAffected = new ArrayList<>();
9981031
try {
999-
boolean batchSupported = true;
1000-
if (!JdbcUtils.supportsBatchUpdates(ps.getConnection())) {
1001-
batchSupported = false;
1002-
logger.debug("JDBC Driver does not support Batch updates; resorting to single statement execution");
1003-
}
1032+
boolean batchSupported = JdbcUtils.supportsBatchUpdates(ps.getConnection());
10041033
int n = 0;
10051034
for (T obj : batchArgs) {
10061035
pss.setValues(ps, obj);
@@ -1038,6 +1067,7 @@ public <T> int[][] batchUpdate(String sql, final Collection<T> batchArgs, final
10381067
return result;
10391068
}
10401069

1070+
10411071
//-------------------------------------------------------------------------
10421072
// Methods dealing with callable statements
10431073
//-------------------------------------------------------------------------

spring-jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCreatorFactory.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,14 @@ public PreparedStatementCreatorFactory(String sql, List<SqlParameter> declaredPa
9191
}
9292

9393

94+
/**
95+
* Return the SQL statement to execute.
96+
* @since 5.1.3
97+
*/
98+
public final String getSql() {
99+
return this.sql;
100+
}
101+
94102
/**
95103
* Add a new declared parameter.
96104
* <p>Order of parameter addition is significant.
@@ -196,7 +204,7 @@ public PreparedStatementCreatorImpl(String actualSql, List<?> parameters) {
196204
Assert.notNull(parameters, "Parameters List must not be null");
197205
this.parameters = parameters;
198206
if (this.parameters.size() != declaredParameters.size()) {
199-
// account for named parameters being used multiple times
207+
// Account for named parameters being used multiple times
200208
Set<String> names = new HashSet<>();
201209
for (int i = 0; i < parameters.size(); i++) {
202210
Object param = parameters.get(i);

spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterBatchUpdateUtils.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import java.sql.SQLException;
2121

2222
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
23-
import org.springframework.jdbc.core.BatchUpdateUtils;
2423
import org.springframework.jdbc.core.JdbcOperations;
2524

2625
/**
@@ -30,8 +29,10 @@
3029
* @author Thomas Risberg
3130
* @author Juergen Hoeller
3231
* @since 3.0
32+
* @deprecated as of 5.1.3, not used by {@link NamedParameterJdbcTemplate} anymore
3333
*/
34-
public abstract class NamedParameterBatchUpdateUtils extends BatchUpdateUtils {
34+
@Deprecated
35+
public abstract class NamedParameterBatchUpdateUtils extends org.springframework.jdbc.core.BatchUpdateUtils {
3536

3637
public static int[] executeBatchUpdateWithNamedParameters(
3738
final ParsedSql parsedSql, final SqlParameterSource[] batchArgs, JdbcOperations jdbcOperations) {

spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.jdbc.core.namedparam;
1818

19+
import java.sql.PreparedStatement;
20+
import java.sql.SQLException;
1921
import java.util.LinkedHashMap;
2022
import java.util.List;
2123
import java.util.Map;
@@ -24,6 +26,7 @@
2426

2527
import org.springframework.dao.DataAccessException;
2628
import org.springframework.dao.support.DataAccessUtils;
29+
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
2730
import org.springframework.jdbc.core.ColumnMapRowMapper;
2831
import org.springframework.jdbc.core.JdbcOperations;
2932
import org.springframework.jdbc.core.JdbcTemplate;
@@ -352,8 +355,26 @@ public int[] batchUpdate(String sql, Map<String, ?>[] batchValues) {
352355

353356
@Override
354357
public int[] batchUpdate(String sql, SqlParameterSource[] batchArgs) {
355-
return NamedParameterBatchUpdateUtils.executeBatchUpdateWithNamedParameters(
356-
getParsedSql(sql), batchArgs, getJdbcOperations());
358+
if (batchArgs.length == 0) {
359+
return new int[0];
360+
}
361+
362+
ParsedSql parsedSql = getParsedSql(sql);
363+
PreparedStatementCreatorFactory pscf = getPreparedStatementCreatorFactory(parsedSql, batchArgs[0]);
364+
365+
return getJdbcOperations().batchUpdate(
366+
pscf.getSql(),
367+
new BatchPreparedStatementSetter() {
368+
@Override
369+
public void setValues(PreparedStatement ps, int i) throws SQLException {
370+
Object[] values = NamedParameterUtils.buildValueArray(parsedSql, batchArgs[i], null);
371+
pscf.newPreparedStatementSetter(values).setValues(ps);
372+
}
373+
@Override
374+
public int getBatchSize() {
375+
return batchArgs.length;
376+
}
377+
});
357378
}
358379

359380

@@ -389,9 +410,7 @@ protected PreparedStatementCreator getPreparedStatementCreator(String sql, SqlPa
389410
@Nullable Consumer<PreparedStatementCreatorFactory> customizer) {
390411

391412
ParsedSql parsedSql = getParsedSql(sql);
392-
String sqlToUse = NamedParameterUtils.substituteNamedParameters(parsedSql, paramSource);
393-
List<SqlParameter> declaredParameters = NamedParameterUtils.buildSqlParameterList(parsedSql, paramSource);
394-
PreparedStatementCreatorFactory pscf = new PreparedStatementCreatorFactory(sqlToUse, declaredParameters);
413+
PreparedStatementCreatorFactory pscf = getPreparedStatementCreatorFactory(parsedSql, paramSource);
395414
if (customizer != null) {
396415
customizer.accept(pscf);
397416
}
@@ -419,4 +438,21 @@ protected ParsedSql getParsedSql(String sql) {
419438
}
420439
}
421440

441+
/**
442+
* Build a {@link PreparedStatementCreatorFactory} based on the given SQL and named parameters.
443+
* @param parsedSql parsed representation of the given SQL statement
444+
* @param paramSource container of arguments to bind
445+
* @return the corresponding {@link PreparedStatementCreatorFactory}
446+
* @since 5.1.3
447+
* @see #getPreparedStatementCreator(String, SqlParameterSource, Consumer)
448+
* @see #getParsedSql(String)
449+
*/
450+
protected PreparedStatementCreatorFactory getPreparedStatementCreatorFactory(
451+
ParsedSql parsedSql, SqlParameterSource paramSource) {
452+
453+
String sqlToUse = NamedParameterUtils.substituteNamedParameters(parsedSql, paramSource);
454+
List<SqlParameter> declaredParameters = NamedParameterUtils.buildSqlParameterList(parsedSql, paramSource);
455+
return new PreparedStatementCreatorFactory(sqlToUse, declaredParameters);
456+
}
457+
422458
}

spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplateTests.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.junit.Rule;
3636
import org.junit.Test;
3737
import org.junit.rules.ExpectedException;
38+
import org.mockito.InOrder;
3839

3940
import org.springframework.jdbc.Customer;
4041
import org.springframework.jdbc.core.JdbcOperations;
@@ -50,6 +51,7 @@
5051
* @author Juergen Hoeller
5152
* @author Chris Beams
5253
* @author Nikita Khateev
54+
* @author Fedor Bobin
5355
*/
5456
public class NamedParameterJdbcTemplateTests {
5557

@@ -468,6 +470,41 @@ public void testBatchUpdateWithSqlParameterSource() throws Exception {
468470
verify(connection, atLeastOnce()).close();
469471
}
470472

473+
@Test
474+
public void testBatchUpdateWithInClause() throws Exception {
475+
@SuppressWarnings("unchecked")
476+
Map<String, Object>[] parameters = new Map[2];
477+
parameters[0] = Collections.singletonMap("ids", Arrays.asList(1, 2));
478+
parameters[1] = Collections.singletonMap("ids", Arrays.asList(3, 4));
479+
480+
final int[] rowsAffected = new int[] {1, 2};
481+
given(preparedStatement.executeBatch()).willReturn(rowsAffected);
482+
given(connection.getMetaData()).willReturn(databaseMetaData);
483+
484+
JdbcTemplate template = new JdbcTemplate(dataSource, false);
485+
namedParameterTemplate = new NamedParameterJdbcTemplate(template);
486+
487+
int[] actualRowsAffected = namedParameterTemplate.batchUpdate(
488+
"delete sometable where id in (:ids)",
489+
parameters
490+
);
491+
492+
assertEquals("executed 2 updates", 2, actualRowsAffected.length);
493+
494+
InOrder inOrder = inOrder(preparedStatement);
495+
496+
inOrder.verify(preparedStatement).setObject(1, 1);
497+
inOrder.verify(preparedStatement).setObject(2, 2);
498+
inOrder.verify(preparedStatement).addBatch();
499+
500+
inOrder.verify(preparedStatement).setObject(1, 3);
501+
inOrder.verify(preparedStatement).setObject(2, 4);
502+
inOrder.verify(preparedStatement).addBatch();
503+
504+
inOrder.verify(preparedStatement, atLeastOnce()).close();
505+
verify(connection, atLeastOnce()).close();
506+
}
507+
471508
@Test
472509
public void testBatchUpdateWithSqlParameterSourcePlusTypeInfo() throws Exception {
473510
SqlParameterSource[] ids = new SqlParameterSource[2];

0 commit comments

Comments
 (0)