diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/BatchUpdateUtils.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/BatchUpdateUtils.java index ac54080f42..560392e969 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/BatchUpdateUtils.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/BatchUpdateUtils.java @@ -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( diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/CallableStatementCreatorFactory.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/CallableStatementCreatorFactory.java index d0db3af3ae..4ad44f3e60 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/CallableStatementCreatorFactory.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/CallableStatementCreatorFactory.java @@ -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. *

Order of parameter addition is significant. diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java index 2a21d16b37..5e35f621b7 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java @@ -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 */ int[][] batchUpdate(String sql, Collection batchArgs, int batchSize, ParameterizedPreparedStatementSetter pss) throws DataAccessException; diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java index 6cdd002c9d..1c454db555 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java @@ -982,8 +982,41 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { } @Override - public int[] batchUpdate(String sql, List batchArgs, int[] argTypes) throws DataAccessException { - return BatchUpdateUtils.executeBatchUpdate(sql, batchArgs, argTypes, this); + public int[] batchUpdate(String sql, List 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 { int[][] result = execute(sql, (PreparedStatementCallback) ps -> { List 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 { return result; } + //------------------------------------------------------------------------- // Methods dealing with callable statements //------------------------------------------------------------------------- diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCreatorFactory.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCreatorFactory.java index 5ce48f5a61..2fb4b84051 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCreatorFactory.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCreatorFactory.java @@ -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. *

Order of parameter addition is significant. @@ -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 names = new HashSet<>(); for (int i = 0; i < parameters.size(); i++) { Object param = parameters.get(i); diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterBatchUpdateUtils.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterBatchUpdateUtils.java index 1720f26579..8dbceccd26 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterBatchUpdateUtils.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterBatchUpdateUtils.java @@ -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; * @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) { diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java index 288e97f92e..eba270006c 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java @@ -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; 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 @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 @Nullable Consumer customizer) { ParsedSql parsedSql = getParsedSql(sql); - String sqlToUse = NamedParameterUtils.substituteNamedParameters(parsedSql, paramSource); - List 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 } } + /** + * 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 declaredParameters = NamedParameterUtils.buildSqlParameterList(parsedSql, paramSource); + return new PreparedStatementCreatorFactory(sqlToUse, declaredParameters); + } + } diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplateTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplateTests.java index dc3a0dab60..6d30d9980e 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplateTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplateTests.java @@ -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.*; * @author Juergen Hoeller * @author Chris Beams * @author Nikita Khateev + * @author Fedor Bobin */ public class NamedParameterJdbcTemplateTests { @@ -468,6 +470,41 @@ public class NamedParameterJdbcTemplateTests { verify(connection, atLeastOnce()).close(); } + @Test + public void testBatchUpdateWithInClause() throws Exception { + @SuppressWarnings("unchecked") + Map[] 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];