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
This commit is contained in:
parent
2a5d7690b6
commit
a3d763d137
|
@ -29,7 +29,9 @@ import org.springframework.lang.Nullable;
|
||||||
* @author Thomas Risberg
|
* @author Thomas Risberg
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
|
* @deprecated as of 5.1.3, not used by {@link JdbcTemplate} anymore
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public abstract class BatchUpdateUtils {
|
public abstract class BatchUpdateUtils {
|
||||||
|
|
||||||
public static int[] executeBatchUpdate(
|
public static int[] executeBatchUpdate(
|
||||||
|
|
|
@ -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.
|
* Add a new declared parameter.
|
||||||
* <p>Order of parameter addition is significant.
|
* <p>Order of parameter addition is significant.
|
||||||
|
|
|
@ -933,6 +933,7 @@ public interface JdbcOperations {
|
||||||
* @param pss the ParameterizedPreparedStatementSetter to use
|
* @param pss the ParameterizedPreparedStatementSetter to use
|
||||||
* @return an array containing for each batch another array containing the numbers of rows affected
|
* @return an array containing for each batch another array containing the numbers of rows affected
|
||||||
* by each update in the batch
|
* by each update in the batch
|
||||||
|
* @since 3.1
|
||||||
*/
|
*/
|
||||||
<T> int[][] batchUpdate(String sql, Collection<T> batchArgs, int batchSize,
|
<T> int[][] batchUpdate(String sql, Collection<T> batchArgs, int batchSize,
|
||||||
ParameterizedPreparedStatementSetter<T> pss) throws DataAccessException;
|
ParameterizedPreparedStatementSetter<T> pss) throws DataAccessException;
|
||||||
|
|
|
@ -982,8 +982,41 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int[] batchUpdate(String sql, List<Object[]> batchArgs, int[] argTypes) throws DataAccessException {
|
public int[] batchUpdate(String sql, List<Object[]> batchArgs, final int[] argTypes) throws DataAccessException {
|
||||||
return BatchUpdateUtils.executeBatchUpdate(sql, batchArgs, argTypes, this);
|
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
|
@Override
|
||||||
|
@ -996,11 +1029,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
|
||||||
int[][] result = execute(sql, (PreparedStatementCallback<int[][]>) ps -> {
|
int[][] result = execute(sql, (PreparedStatementCallback<int[][]>) ps -> {
|
||||||
List<int[]> rowsAffected = new ArrayList<>();
|
List<int[]> rowsAffected = new ArrayList<>();
|
||||||
try {
|
try {
|
||||||
boolean batchSupported = true;
|
boolean batchSupported = JdbcUtils.supportsBatchUpdates(ps.getConnection());
|
||||||
if (!JdbcUtils.supportsBatchUpdates(ps.getConnection())) {
|
|
||||||
batchSupported = false;
|
|
||||||
logger.debug("JDBC Driver does not support Batch updates; resorting to single statement execution");
|
|
||||||
}
|
|
||||||
int n = 0;
|
int n = 0;
|
||||||
for (T obj : batchArgs) {
|
for (T obj : batchArgs) {
|
||||||
pss.setValues(ps, obj);
|
pss.setValues(ps, obj);
|
||||||
|
@ -1038,6 +1067,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//-------------------------------------------------------------------------
|
//-------------------------------------------------------------------------
|
||||||
// Methods dealing with callable statements
|
// Methods dealing with callable statements
|
||||||
//-------------------------------------------------------------------------
|
//-------------------------------------------------------------------------
|
||||||
|
|
|
@ -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.
|
* Add a new declared parameter.
|
||||||
* <p>Order of parameter addition is significant.
|
* <p>Order of parameter addition is significant.
|
||||||
|
@ -196,7 +204,7 @@ public class PreparedStatementCreatorFactory {
|
||||||
Assert.notNull(parameters, "Parameters List must not be null");
|
Assert.notNull(parameters, "Parameters List must not be null");
|
||||||
this.parameters = parameters;
|
this.parameters = parameters;
|
||||||
if (this.parameters.size() != declaredParameters.size()) {
|
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<>();
|
Set<String> names = new HashSet<>();
|
||||||
for (int i = 0; i < parameters.size(); i++) {
|
for (int i = 0; i < parameters.size(); i++) {
|
||||||
Object param = parameters.get(i);
|
Object param = parameters.get(i);
|
||||||
|
|
|
@ -20,7 +20,6 @@ import java.sql.PreparedStatement;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
|
||||||
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
|
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
|
||||||
import org.springframework.jdbc.core.BatchUpdateUtils;
|
|
||||||
import org.springframework.jdbc.core.JdbcOperations;
|
import org.springframework.jdbc.core.JdbcOperations;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,8 +29,10 @@ import org.springframework.jdbc.core.JdbcOperations;
|
||||||
* @author Thomas Risberg
|
* @author Thomas Risberg
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
* @since 3.0
|
* @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(
|
public static int[] executeBatchUpdateWithNamedParameters(
|
||||||
final ParsedSql parsedSql, final SqlParameterSource[] batchArgs, JdbcOperations jdbcOperations) {
|
final ParsedSql parsedSql, final SqlParameterSource[] batchArgs, JdbcOperations jdbcOperations) {
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.springframework.jdbc.core.namedparam;
|
package org.springframework.jdbc.core.namedparam;
|
||||||
|
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.SQLException;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -24,6 +26,7 @@ import javax.sql.DataSource;
|
||||||
|
|
||||||
import org.springframework.dao.DataAccessException;
|
import org.springframework.dao.DataAccessException;
|
||||||
import org.springframework.dao.support.DataAccessUtils;
|
import org.springframework.dao.support.DataAccessUtils;
|
||||||
|
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
|
||||||
import org.springframework.jdbc.core.ColumnMapRowMapper;
|
import org.springframework.jdbc.core.ColumnMapRowMapper;
|
||||||
import org.springframework.jdbc.core.JdbcOperations;
|
import org.springframework.jdbc.core.JdbcOperations;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
@ -352,8 +355,26 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int[] batchUpdate(String sql, SqlParameterSource[] batchArgs) {
|
public int[] batchUpdate(String sql, SqlParameterSource[] batchArgs) {
|
||||||
return NamedParameterBatchUpdateUtils.executeBatchUpdateWithNamedParameters(
|
if (batchArgs.length == 0) {
|
||||||
getParsedSql(sql), batchArgs, getJdbcOperations());
|
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
|
||||||
@Nullable Consumer<PreparedStatementCreatorFactory> customizer) {
|
@Nullable Consumer<PreparedStatementCreatorFactory> customizer) {
|
||||||
|
|
||||||
ParsedSql parsedSql = getParsedSql(sql);
|
ParsedSql parsedSql = getParsedSql(sql);
|
||||||
String sqlToUse = NamedParameterUtils.substituteNamedParameters(parsedSql, paramSource);
|
PreparedStatementCreatorFactory pscf = getPreparedStatementCreatorFactory(parsedSql, paramSource);
|
||||||
List<SqlParameter> declaredParameters = NamedParameterUtils.buildSqlParameterList(parsedSql, paramSource);
|
|
||||||
PreparedStatementCreatorFactory pscf = new PreparedStatementCreatorFactory(sqlToUse, declaredParameters);
|
|
||||||
if (customizer != null) {
|
if (customizer != null) {
|
||||||
customizer.accept(pscf);
|
customizer.accept(pscf);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ import org.junit.Ignore;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.ExpectedException;
|
import org.junit.rules.ExpectedException;
|
||||||
|
import org.mockito.InOrder;
|
||||||
|
|
||||||
import org.springframework.jdbc.Customer;
|
import org.springframework.jdbc.Customer;
|
||||||
import org.springframework.jdbc.core.JdbcOperations;
|
import org.springframework.jdbc.core.JdbcOperations;
|
||||||
|
@ -50,6 +51,7 @@ import static org.mockito.BDDMockito.*;
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
* @author Chris Beams
|
* @author Chris Beams
|
||||||
* @author Nikita Khateev
|
* @author Nikita Khateev
|
||||||
|
* @author Fedor Bobin
|
||||||
*/
|
*/
|
||||||
public class NamedParameterJdbcTemplateTests {
|
public class NamedParameterJdbcTemplateTests {
|
||||||
|
|
||||||
|
@ -468,6 +470,41 @@ public class NamedParameterJdbcTemplateTests {
|
||||||
verify(connection, atLeastOnce()).close();
|
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
|
@Test
|
||||||
public void testBatchUpdateWithSqlParameterSourcePlusTypeInfo() throws Exception {
|
public void testBatchUpdateWithSqlParameterSourcePlusTypeInfo() throws Exception {
|
||||||
SqlParameterSource[] ids = new SqlParameterSource[2];
|
SqlParameterSource[] ids = new SqlParameterSource[2];
|
||||||
|
|
Loading…
Reference in New Issue