From 362c59c3102acb68145cf1689b6391cb1fb94486 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 8 Nov 2018 13:43:07 +0100 Subject: [PATCH] Consistently return empty array in case of empty batch arguments Issue: SPR-17476 --- .../jdbc/core/BatchUpdateUtils.java | 13 ++-- .../NamedParameterBatchUpdateUtils.java | 11 ++-- .../jdbc/core/JdbcTemplateTests.java | 60 +++++++++++-------- .../NamedParameterJdbcTemplateTests.java | 35 ++++++----- 4 files changed, 69 insertions(+), 50 deletions(-) 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 fbf1ea93450..ac54080f422 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,24 +27,29 @@ import org.springframework.lang.Nullable; * Mainly for internal use within the framework. * * @author Thomas Risberg + * @author Juergen Hoeller * @since 3.0 */ public abstract class BatchUpdateUtils { public static int[] executeBatchUpdate( - String sql, final List batchValues, final int[] columnTypes, JdbcOperations jdbcOperations) { + String sql, final List batchArgs, final int[] columnTypes, JdbcOperations jdbcOperations) { + + if (batchArgs.isEmpty()) { + return new int[0]; + } return jdbcOperations.batchUpdate( sql, new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { - Object[] values = batchValues.get(i); + Object[] values = batchArgs.get(i); setStatementParameters(values, ps, columnTypes); } @Override public int getBatchSize() { - return batchValues.size(); + return batchArgs.size(); } }); } 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 af6367e33a6..1720f265793 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,15 +28,16 @@ import org.springframework.jdbc.core.JdbcOperations; * Mainly for internal use within the framework. * * @author Thomas Risberg + * @author Juergen Hoeller * @since 3.0 */ public abstract class NamedParameterBatchUpdateUtils extends BatchUpdateUtils { - public static int[] executeBatchUpdateWithNamedParameters(final ParsedSql parsedSql, - final SqlParameterSource[] batchArgs, JdbcOperations jdbcOperations) { + public static int[] executeBatchUpdateWithNamedParameters( + final ParsedSql parsedSql, final SqlParameterSource[] batchArgs, JdbcOperations jdbcOperations) { - if (batchArgs.length <= 0) { - return new int[] {0}; + if (batchArgs.length == 0) { + return new int[0]; } String sqlToUse = NamedParameterUtils.substituteNamedParameters(parsedSql, batchArgs[0]); diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateTests.java index 3db0188b9ce..3c577a8fa8c 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateTests.java @@ -192,7 +192,7 @@ public class JdbcTemplateTests { Object argument, JdbcTemplateCallback jdbcTemplateCallback) throws Exception { String sql = "SELECT FORENAME FROM CUSTMR"; - String[] results = { "rod", "gary", " portia" }; + String[] results = {"rod", "gary", " portia"}; class StringHandler implements RowCallbackHandler { private List list = new LinkedList<>(); @@ -491,8 +491,8 @@ public class JdbcTemplateTests { @Test public void testBatchUpdateWithPreparedStatement() throws Exception { final String sql = "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = ?"; - final int[] ids = new int[] { 100, 200 }; - final int[] rowsAffected = new int[] { 1, 2 }; + final int[] ids = new int[] {100, 200}; + final int[] rowsAffected = new int[] {1, 2}; given(this.preparedStatement.executeBatch()).willReturn(rowsAffected); mockDatabaseMetaData(true); @@ -525,8 +525,8 @@ public class JdbcTemplateTests { @Test public void testInterruptibleBatchUpdate() throws Exception { final String sql = "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = ?"; - final int[] ids = new int[] { 100, 200 }; - final int[] rowsAffected = new int[] { 1, 2 }; + final int[] ids = new int[] {100, 200}; + final int[] rowsAffected = new int[] {1, 2}; given(this.preparedStatement.executeBatch()).willReturn(rowsAffected); mockDatabaseMetaData(true); @@ -566,8 +566,8 @@ public class JdbcTemplateTests { @Test public void testInterruptibleBatchUpdateWithBaseClass() throws Exception { final String sql = "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = ?"; - final int[] ids = new int[] { 100, 200 }; - final int[] rowsAffected = new int[] { 1, 2 }; + final int[] ids = new int[] {100, 200}; + final int[] rowsAffected = new int[] {1, 2}; given(this.preparedStatement.executeBatch()).willReturn(rowsAffected); mockDatabaseMetaData(true); @@ -603,8 +603,8 @@ public class JdbcTemplateTests { @Test public void testInterruptibleBatchUpdateWithBaseClassAndNoBatchSupport() throws Exception { final String sql = "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = ?"; - final int[] ids = new int[] { 100, 200 }; - final int[] rowsAffected = new int[] { 1, 2 }; + final int[] ids = new int[] {100, 200}; + final int[] rowsAffected = new int[] {1, 2}; given(this.preparedStatement.executeUpdate()).willReturn(rowsAffected[0], rowsAffected[1]); mockDatabaseMetaData(false); @@ -640,8 +640,8 @@ public class JdbcTemplateTests { @Test public void testBatchUpdateWithPreparedStatementAndNoBatchSupport() throws Exception { final String sql = "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = ?"; - final int[] ids = new int[] { 100, 200 }; - final int[] rowsAffected = new int[] { 1, 2 }; + final int[] ids = new int[] {100, 200}; + final int[] rowsAffected = new int[] {1, 2}; given(this.preparedStatement.executeUpdate()).willReturn(rowsAffected[0], rowsAffected[1]); @@ -671,7 +671,7 @@ public class JdbcTemplateTests { @Test public void testBatchUpdateFails() throws Exception { final String sql = "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = ?"; - final int[] ids = new int[] { 100, 200 }; + final int[] ids = new int[] {100, 200}; SQLException sqlException = new SQLException(); given(this.preparedStatement.executeBatch()).willThrow(sqlException); @@ -702,6 +702,15 @@ public class JdbcTemplateTests { } } + @Test + public void testBatchUpdateWithEmptyList() throws Exception { + final String sql = "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = ?"; + JdbcTemplate template = new JdbcTemplate(this.dataSource, false); + + int[] actualRowsAffected = template.batchUpdate(sql, Collections.emptyList()); + assertTrue("executed 0 updates", actualRowsAffected.length == 0); + } + @Test public void testBatchUpdateWithListOfObjectArrays() throws Exception { final String sql = "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = ?"; @@ -712,11 +721,9 @@ public class JdbcTemplateTests { given(this.preparedStatement.executeBatch()).willReturn(rowsAffected); mockDatabaseMetaData(true); - JdbcTemplate template = new JdbcTemplate(this.dataSource, false); int[] actualRowsAffected = template.batchUpdate(sql, ids); - assertTrue("executed 2 updates", actualRowsAffected.length == 2); assertEquals(rowsAffected[0], actualRowsAffected[0]); assertEquals(rowsAffected[1], actualRowsAffected[1]); @@ -739,10 +746,9 @@ public class JdbcTemplateTests { given(this.preparedStatement.executeBatch()).willReturn(rowsAffected); mockDatabaseMetaData(true); - this.template = new JdbcTemplate(this.dataSource, false); - int[] actualRowsAffected = this.template.batchUpdate(sql, ids, sqlTypes); + int[] actualRowsAffected = this.template.batchUpdate(sql, ids, sqlTypes); assertTrue("executed 2 updates", actualRowsAffected.length == 2); assertEquals(rowsAffected[0], actualRowsAffected[0]); assertEquals(rowsAffected[1], actualRowsAffected[1]); @@ -757,8 +763,8 @@ public class JdbcTemplateTests { public void testBatchUpdateWithCollectionOfObjects() throws Exception { final String sql = "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = ?"; final List ids = Arrays.asList(100, 200, 300); - final int[] rowsAffected1 = new int[] { 1, 2 }; - final int[] rowsAffected2 = new int[] { 3 }; + final int[] rowsAffected1 = new int[] {1, 2}; + final int[] rowsAffected2 = new int[] {3}; given(this.preparedStatement.executeBatch()).willReturn(rowsAffected1, rowsAffected2); mockDatabaseMetaData(true); @@ -781,19 +787,20 @@ public class JdbcTemplateTests { } @Test - public void testCouldntGetConnectionForOperationOrExceptionTranslator() throws SQLException { + public void testCouldNotGetConnectionForOperationOrExceptionTranslator() throws SQLException { SQLException sqlException = new SQLException("foo", "07xxx"); this.dataSource = mock(DataSource.class); given(this.dataSource.getConnection()).willThrow(sqlException); JdbcTemplate template = new JdbcTemplate(this.dataSource, false); RowCountCallbackHandler rcch = new RowCountCallbackHandler(); + this.thrown.expect(CannotGetJdbcConnectionException.class); this.thrown.expect(exceptionCause(sameInstance(sqlException))); template.query("SELECT ID, FORENAME FROM CUSTMR WHERE ID < 3", rcch); } @Test - public void testCouldntGetConnectionForOperationWithLazyExceptionTranslator() throws SQLException { + public void testCouldNotGetConnectionForOperationWithLazyExceptionTranslator() throws SQLException { SQLException sqlException = new SQLException("foo", "07xxx"); this.dataSource = mock(DataSource.class); given(this.dataSource.getConnection()).willThrow(sqlException); @@ -801,30 +808,31 @@ public class JdbcTemplateTests { this.template.setDataSource(this.dataSource); this.template.afterPropertiesSet(); RowCountCallbackHandler rcch = new RowCountCallbackHandler(); + this.thrown.expect(CannotGetJdbcConnectionException.class); this.thrown.expect(exceptionCause(sameInstance(sqlException))); this.template.query("SELECT ID, FORENAME FROM CUSTMR WHERE ID < 3", rcch); } @Test - public void testCouldntGetConnectionInOperationWithExceptionTranslatorInitializedViaBeanProperty() + public void testCouldNotGetConnectionInOperationWithExceptionTranslatorInitializedViaBeanProperty() throws SQLException { - doTestCouldntGetConnectionInOperationWithExceptionTranslatorInitialized(true); + doTestCouldNotGetConnectionInOperationWithExceptionTranslatorInitialized(true); } @Test - public void testCouldntGetConnectionInOperationWithExceptionTranslatorInitializedInAfterPropertiesSet() + public void testCouldNotGetConnectionInOperationWithExceptionTranslatorInitializedInAfterPropertiesSet() throws SQLException { - doTestCouldntGetConnectionInOperationWithExceptionTranslatorInitialized(false); + doTestCouldNotGetConnectionInOperationWithExceptionTranslatorInitialized(false); } /** * If beanProperty is true, initialize via exception translator bean property; * if false, use afterPropertiesSet(). */ - private void doTestCouldntGetConnectionInOperationWithExceptionTranslatorInitialized(boolean beanProperty) + private void doTestCouldNotGetConnectionInOperationWithExceptionTranslatorInitialized(boolean beanProperty) throws SQLException { SQLException sqlException = new SQLException("foo", "07xxx"); @@ -884,7 +892,7 @@ public class JdbcTemplateTests { } @Test - public void testCouldntClose() throws Exception { + public void testCouldNotClose() throws Exception { SQLException sqlException = new SQLException("bar"); given(this.connection.createStatement()).willReturn(this.statement); given(this.resultSet.next()).willReturn(false); 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 951f483fd36..dc3a0dab603 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 @@ -418,12 +418,10 @@ public class NamedParameterJdbcTemplateTests { given(preparedStatement.executeBatch()).willReturn(rowsAffected); given(connection.getMetaData()).willReturn(databaseMetaData); + namedParameterTemplate = new NamedParameterJdbcTemplate(new JdbcTemplate(dataSource, false)); - JdbcTemplate template = new JdbcTemplate(dataSource, false); - namedParameterTemplate = new NamedParameterJdbcTemplate(template); - assertSame(template, namedParameterTemplate.getJdbcTemplate()); - - int[] actualRowsAffected = namedParameterTemplate.batchUpdate("UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = :id", ids); + int[] actualRowsAffected = namedParameterTemplate.batchUpdate( + "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = :id", ids); assertTrue("executed 2 updates", actualRowsAffected.length == 2); assertEquals(rowsAffected[0], actualRowsAffected[0]); assertEquals(rowsAffected[1], actualRowsAffected[1]); @@ -435,6 +433,17 @@ public class NamedParameterJdbcTemplateTests { verify(connection, atLeastOnce()).close(); } + @Test + public void testBatchUpdateWithEmptyMap() throws Exception { + @SuppressWarnings("unchecked") + final Map[] ids = new Map[0]; + namedParameterTemplate = new NamedParameterJdbcTemplate(new JdbcTemplate(dataSource, false)); + + int[] actualRowsAffected = namedParameterTemplate.batchUpdate( + "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = :id", ids); + assertTrue("executed 0 updates", actualRowsAffected.length == 0); + } + @Test public void testBatchUpdateWithSqlParameterSource() throws Exception { SqlParameterSource[] ids = new SqlParameterSource[2]; @@ -444,12 +453,10 @@ public class NamedParameterJdbcTemplateTests { given(preparedStatement.executeBatch()).willReturn(rowsAffected); given(connection.getMetaData()).willReturn(databaseMetaData); + namedParameterTemplate = new NamedParameterJdbcTemplate(new JdbcTemplate(dataSource, false)); - JdbcTemplate template = new JdbcTemplate(dataSource, false); - namedParameterTemplate = new NamedParameterJdbcTemplate(template); - assertSame(template, namedParameterTemplate.getJdbcTemplate()); - - int[] actualRowsAffected = namedParameterTemplate.batchUpdate("UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = :id", ids); + int[] actualRowsAffected = namedParameterTemplate.batchUpdate( + "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = :id", ids); assertTrue("executed 2 updates", actualRowsAffected.length == 2); assertEquals(rowsAffected[0], actualRowsAffected[0]); assertEquals(rowsAffected[1], actualRowsAffected[1]); @@ -470,12 +477,10 @@ public class NamedParameterJdbcTemplateTests { given(preparedStatement.executeBatch()).willReturn(rowsAffected); given(connection.getMetaData()).willReturn(databaseMetaData); + namedParameterTemplate = new NamedParameterJdbcTemplate(new JdbcTemplate(dataSource, false)); - JdbcTemplate template = new JdbcTemplate(dataSource, false); - namedParameterTemplate = new NamedParameterJdbcTemplate(template); - assertSame(template, namedParameterTemplate.getJdbcTemplate()); - - int[] actualRowsAffected = namedParameterTemplate.batchUpdate("UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = :id", ids); + int[] actualRowsAffected = namedParameterTemplate.batchUpdate( + "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = :id", ids); assertTrue("executed 2 updates", actualRowsAffected.length == 2); assertEquals(rowsAffected[0], actualRowsAffected[0]); assertEquals(rowsAffected[1], actualRowsAffected[1]);