Browse Source

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
pull/2023/head
Juergen Hoeller 6 years ago
parent
commit
a3d763d137
  1. 2
      spring-jdbc/src/main/java/org/springframework/jdbc/core/BatchUpdateUtils.java
  2. 8
      spring-jdbc/src/main/java/org/springframework/jdbc/core/CallableStatementCreatorFactory.java
  3. 1
      spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java
  4. 44
      spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java
  5. 10
      spring-jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCreatorFactory.java
  6. 5
      spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterBatchUpdateUtils.java
  7. 46
      spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java
  8. 37
      spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplateTests.java

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

@ -29,7 +29,9 @@ import org.springframework.lang.Nullable; @@ -29,7 +29,9 @@ import org.springframework.lang.Nullable;
* @author Thomas Risberg
* @author Juergen Hoeller
* @since 3.0
* @deprecated as of 5.1.3, not used by {@link JdbcTemplate} anymore
*/
@Deprecated
public abstract class BatchUpdateUtils {
public static int[] executeBatchUpdate(

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

@ -71,6 +71,14 @@ public class CallableStatementCreatorFactory { @@ -71,6 +71,14 @@ public class CallableStatementCreatorFactory {
}
/**
* Return the SQL call string.
* @since 5.1.3
*/
public final String getCallString() {
return this.callString;
}
/**
* Add a new declared parameter.
* <p>Order of parameter addition is significant.

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

@ -933,6 +933,7 @@ public interface JdbcOperations { @@ -933,6 +933,7 @@ public interface JdbcOperations {
* @param pss the ParameterizedPreparedStatementSetter to use
* @return an array containing for each batch another array containing the numbers of rows affected
* by each update in the batch
* @since 3.1
*/
<T> int[][] batchUpdate(String sql, Collection<T> batchArgs, int batchSize,
ParameterizedPreparedStatementSetter<T> pss) throws DataAccessException;

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

@ -982,8 +982,41 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { @@ -982,8 +982,41 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
}
@Override
public int[] batchUpdate(String sql, List<Object[]> batchArgs, int[] argTypes) throws DataAccessException {
return BatchUpdateUtils.executeBatchUpdate(sql, batchArgs, argTypes, this);
public int[] batchUpdate(String sql, List<Object[]> batchArgs, final int[] argTypes) throws DataAccessException {
if (batchArgs.isEmpty()) {
return new int[0];
}
return batchUpdate(
sql,
new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
Object[] values = batchArgs.get(i);
int colIndex = 0;
for (Object value : values) {
colIndex++;
if (value instanceof SqlParameterValue) {
SqlParameterValue paramValue = (SqlParameterValue) value;
StatementCreatorUtils.setParameterValue(ps, colIndex, paramValue, paramValue.getValue());
}
else {
int colType;
if (argTypes.length < colIndex) {
colType = SqlTypeValue.TYPE_UNKNOWN;
}
else {
colType = argTypes[colIndex - 1];
}
StatementCreatorUtils.setParameterValue(ps, colIndex, colType, value);
}
}
}
@Override
public int getBatchSize() {
return batchArgs.size();
}
});
}
@Override
@ -996,11 +1029,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { @@ -996,11 +1029,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
int[][] result = execute(sql, (PreparedStatementCallback<int[][]>) ps -> {
List<int[]> rowsAffected = new ArrayList<>();
try {
boolean batchSupported = true;
if (!JdbcUtils.supportsBatchUpdates(ps.getConnection())) {
batchSupported = false;
logger.debug("JDBC Driver does not support Batch updates; resorting to single statement execution");
}
boolean batchSupported = JdbcUtils.supportsBatchUpdates(ps.getConnection());
int n = 0;
for (T obj : batchArgs) {
pss.setValues(ps, obj);
@ -1038,6 +1067,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { @@ -1038,6 +1067,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
return result;
}
//-------------------------------------------------------------------------
// Methods dealing with callable statements
//-------------------------------------------------------------------------

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

@ -91,6 +91,14 @@ public class PreparedStatementCreatorFactory { @@ -91,6 +91,14 @@ public class PreparedStatementCreatorFactory {
}
/**
* Return the SQL statement to execute.
* @since 5.1.3
*/
public final String getSql() {
return this.sql;
}
/**
* Add a new declared parameter.
* <p>Order of parameter addition is significant.
@ -196,7 +204,7 @@ public class PreparedStatementCreatorFactory { @@ -196,7 +204,7 @@ public class PreparedStatementCreatorFactory {
Assert.notNull(parameters, "Parameters List must not be null");
this.parameters = parameters;
if (this.parameters.size() != declaredParameters.size()) {
// account for named parameters being used multiple times
// Account for named parameters being used multiple times
Set<String> names = new HashSet<>();
for (int i = 0; i < parameters.size(); i++) {
Object param = parameters.get(i);

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

@ -20,7 +20,6 @@ import java.sql.PreparedStatement; @@ -20,7 +20,6 @@ import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.BatchUpdateUtils;
import org.springframework.jdbc.core.JdbcOperations;
/**
@ -30,8 +29,10 @@ import org.springframework.jdbc.core.JdbcOperations; @@ -30,8 +29,10 @@ import org.springframework.jdbc.core.JdbcOperations;
* @author Thomas Risberg
* @author Juergen Hoeller
* @since 3.0
* @deprecated as of 5.1.3, not used by {@link NamedParameterJdbcTemplate} anymore
*/
public abstract class NamedParameterBatchUpdateUtils extends BatchUpdateUtils {
@Deprecated
public abstract class NamedParameterBatchUpdateUtils extends org.springframework.jdbc.core.BatchUpdateUtils {
public static int[] executeBatchUpdateWithNamedParameters(
final ParsedSql parsedSql, final SqlParameterSource[] batchArgs, JdbcOperations jdbcOperations) {

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

@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
package org.springframework.jdbc.core.namedparam;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@ -24,6 +26,7 @@ import javax.sql.DataSource; @@ -24,6 +26,7 @@ import javax.sql.DataSource;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.DataAccessUtils;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.ColumnMapRowMapper;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
@ -352,8 +355,26 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations @@ -352,8 +355,26 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations
@Override
public int[] batchUpdate(String sql, SqlParameterSource[] batchArgs) {
return NamedParameterBatchUpdateUtils.executeBatchUpdateWithNamedParameters(
getParsedSql(sql), batchArgs, getJdbcOperations());
if (batchArgs.length == 0) {
return new int[0];
}
ParsedSql parsedSql = getParsedSql(sql);
PreparedStatementCreatorFactory pscf = getPreparedStatementCreatorFactory(parsedSql, batchArgs[0]);
return getJdbcOperations().batchUpdate(
pscf.getSql(),
new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
Object[] values = NamedParameterUtils.buildValueArray(parsedSql, batchArgs[i], null);
pscf.newPreparedStatementSetter(values).setValues(ps);
}
@Override
public int getBatchSize() {
return batchArgs.length;
}
});
}
@ -389,9 +410,7 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations @@ -389,9 +410,7 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations
@Nullable Consumer<PreparedStatementCreatorFactory> customizer) {
ParsedSql parsedSql = getParsedSql(sql);
String sqlToUse = NamedParameterUtils.substituteNamedParameters(parsedSql, paramSource);
List<SqlParameter> declaredParameters = NamedParameterUtils.buildSqlParameterList(parsedSql, paramSource);
PreparedStatementCreatorFactory pscf = new PreparedStatementCreatorFactory(sqlToUse, declaredParameters);
PreparedStatementCreatorFactory pscf = getPreparedStatementCreatorFactory(parsedSql, paramSource);
if (customizer != null) {
customizer.accept(pscf);
}
@ -419,4 +438,21 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations @@ -419,4 +438,21 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations
}
}
/**
* Build a {@link PreparedStatementCreatorFactory} based on the given SQL and named parameters.
* @param parsedSql parsed representation of the given SQL statement
* @param paramSource container of arguments to bind
* @return the corresponding {@link PreparedStatementCreatorFactory}
* @since 5.1.3
* @see #getPreparedStatementCreator(String, SqlParameterSource, Consumer)
* @see #getParsedSql(String)
*/
protected PreparedStatementCreatorFactory getPreparedStatementCreatorFactory(
ParsedSql parsedSql, SqlParameterSource paramSource) {
String sqlToUse = NamedParameterUtils.substituteNamedParameters(parsedSql, paramSource);
List<SqlParameter> declaredParameters = NamedParameterUtils.buildSqlParameterList(parsedSql, paramSource);
return new PreparedStatementCreatorFactory(sqlToUse, declaredParameters);
}
}

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

@ -35,6 +35,7 @@ import org.junit.Ignore; @@ -35,6 +35,7 @@ import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.InOrder;
import org.springframework.jdbc.Customer;
import org.springframework.jdbc.core.JdbcOperations;
@ -50,6 +51,7 @@ import static org.mockito.BDDMockito.*; @@ -50,6 +51,7 @@ import static org.mockito.BDDMockito.*;
* @author Juergen Hoeller
* @author Chris Beams
* @author Nikita Khateev
* @author Fedor Bobin
*/
public class NamedParameterJdbcTemplateTests {
@ -468,6 +470,41 @@ public class NamedParameterJdbcTemplateTests { @@ -468,6 +470,41 @@ public class NamedParameterJdbcTemplateTests {
verify(connection, atLeastOnce()).close();
}
@Test
public void testBatchUpdateWithInClause() throws Exception {
@SuppressWarnings("unchecked")
Map<String, Object>[] parameters = new Map[2];
parameters[0] = Collections.singletonMap("ids", Arrays.asList(1, 2));
parameters[1] = Collections.singletonMap("ids", Arrays.asList(3, 4));
final int[] rowsAffected = new int[] {1, 2};
given(preparedStatement.executeBatch()).willReturn(rowsAffected);
given(connection.getMetaData()).willReturn(databaseMetaData);
JdbcTemplate template = new JdbcTemplate(dataSource, false);
namedParameterTemplate = new NamedParameterJdbcTemplate(template);
int[] actualRowsAffected = namedParameterTemplate.batchUpdate(
"delete sometable where id in (:ids)",
parameters
);
assertEquals("executed 2 updates", 2, actualRowsAffected.length);
InOrder inOrder = inOrder(preparedStatement);
inOrder.verify(preparedStatement).setObject(1, 1);
inOrder.verify(preparedStatement).setObject(2, 2);
inOrder.verify(preparedStatement).addBatch();
inOrder.verify(preparedStatement).setObject(1, 3);
inOrder.verify(preparedStatement).setObject(2, 4);
inOrder.verify(preparedStatement).addBatch();
inOrder.verify(preparedStatement, atLeastOnce()).close();
verify(connection, atLeastOnce()).close();
}
@Test
public void testBatchUpdateWithSqlParameterSourcePlusTypeInfo() throws Exception {
SqlParameterSource[] ids = new SqlParameterSource[2];

Loading…
Cancel
Save