From a72bf1a65d48dcdc9cc9b7b7e4d5894a297d7b14 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 11 Oct 2023 09:52:13 +0200 Subject: [PATCH] Polishing. Delegate BatchJdbcOperations calls to NamedParameterJdbcOperations. Refine since and deprecation tags. See #1616 --- .../core/convert/BatchJdbcOperations.java | 117 ++---------------- .../core/convert/InsertStrategyFactory.java | 6 +- .../SimpleJdbcRepositoryEventsUnitTests.java | 40 +++--- .../ROOT/pages/jdbc/entity-persistence.adoc | 12 +- 4 files changed, 41 insertions(+), 134 deletions(-) diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BatchJdbcOperations.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BatchJdbcOperations.java index 6702005b..a0047af2 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BatchJdbcOperations.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BatchJdbcOperations.java @@ -15,49 +15,35 @@ */ package org.springframework.data.jdbc.core.convert; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.springframework.jdbc.core.BatchPreparedStatementSetter; -import org.springframework.jdbc.core.ColumnMapRowMapper; import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.PreparedStatementCallback; -import org.springframework.jdbc.core.PreparedStatementCreator; -import org.springframework.jdbc.core.PreparedStatementCreatorFactory; -import org.springframework.jdbc.core.RowMapperResultSetExtractor; -import org.springframework.jdbc.core.SqlParameter; -import org.springframework.jdbc.core.namedparam.NamedParameterUtils; -import org.springframework.jdbc.core.namedparam.ParsedSql; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.SqlParameterSource; -import org.springframework.jdbc.support.JdbcUtils; import org.springframework.jdbc.support.KeyHolder; import org.springframework.lang.Nullable; -import org.springframework.util.Assert; /** * Counterpart to {@link org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations} containing methods for * performing batch updates with generated keys. * * @author Chirag Tailor + * @author Mark Paluch * @since 2.4 - * @deprecated Use the standard {@link org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations} instead. + * @deprecated since 3.2. Use {@link org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations#batchUpdate} + * methods instead. */ @Deprecated(since = "3.2") public class BatchJdbcOperations { - private final JdbcOperations jdbcOperations; + private final NamedParameterJdbcOperations jdbcOperations; public BatchJdbcOperations(JdbcOperations jdbcOperations) { - this.jdbcOperations = jdbcOperations; + this.jdbcOperations = new NamedParameterJdbcTemplate(jdbcOperations); } /** * Execute a batch using the supplied SQL statement with the batch of supplied arguments, returning generated keys. - * + * * @param sql the SQL statement to execute * @param batchArgs the array of {@link SqlParameterSource} containing the batch of arguments for the query * @param generatedKeyHolder a {@link KeyHolder} that will hold the generated keys @@ -69,12 +55,12 @@ public class BatchJdbcOperations { * @since 2.4 */ int[] batchUpdate(String sql, SqlParameterSource[] batchArgs, KeyHolder generatedKeyHolder) { - return batchUpdate(sql, batchArgs, generatedKeyHolder, null); + return jdbcOperations.batchUpdate(sql, batchArgs, generatedKeyHolder); } /** * Execute a batch using the supplied SQL statement with the batch of supplied arguments, returning generated keys. - * + * * @param sql the SQL statement to execute * @param batchArgs the array of {@link SqlParameterSource} containing the batch of arguments for the query * @param generatedKeyHolder a {@link KeyHolder} that will hold the generated keys @@ -88,87 +74,6 @@ public class BatchJdbcOperations { */ int[] batchUpdate(String sql, SqlParameterSource[] batchArgs, KeyHolder generatedKeyHolder, @Nullable String[] keyColumnNames) { - - if (batchArgs.length == 0) { - return new int[0]; - } - - ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql); - SqlParameterSource paramSource = batchArgs[0]; - String sqlToUse = NamedParameterUtils.substituteNamedParameters(parsedSql, paramSource); - List declaredParameters = NamedParameterUtils.buildSqlParameterList(parsedSql, paramSource); - PreparedStatementCreatorFactory pscf = new PreparedStatementCreatorFactory(sqlToUse, declaredParameters); - if (keyColumnNames != null) { - pscf.setGeneratedKeysColumnNames(keyColumnNames); - } else { - pscf.setReturnGeneratedKeys(true); - } - Object[] params = NamedParameterUtils.buildValueArray(parsedSql, paramSource, null); - PreparedStatementCreator psc = pscf.newPreparedStatementCreator(params); - BatchPreparedStatementSetter bpss = 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; - } - }; - PreparedStatementCallback preparedStatementCallback = ps -> { - - int batchSize = bpss.getBatchSize(); - generatedKeyHolder.getKeyList().clear(); - if (JdbcUtils.supportsBatchUpdates(ps.getConnection())) { - - for (int i = 0; i < batchSize; i++) { - - bpss.setValues(ps, i); - ps.addBatch(); - } - int[] results = ps.executeBatch(); - storeGeneratedKeys(generatedKeyHolder, ps, batchSize); - return results; - } else { - - List rowsAffected = new ArrayList<>(); - for (int i = 0; i < batchSize; i++) { - - bpss.setValues(ps, i); - rowsAffected.add(ps.executeUpdate()); - storeGeneratedKeys(generatedKeyHolder, ps, 1); - } - int[] rowsAffectedArray = new int[rowsAffected.size()]; - for (int i = 0; i < rowsAffectedArray.length; i++) { - rowsAffectedArray[i] = rowsAffected.get(i); - } - return rowsAffectedArray; - } - }; - int[] result = jdbcOperations.execute(psc, preparedStatementCallback); - Assert.state(result != null, "No result array"); - return result; - } - - private void storeGeneratedKeys(KeyHolder generatedKeyHolder, PreparedStatement ps, int rowsExpected) - throws SQLException { - - List> generatedKeys = generatedKeyHolder.getKeyList(); - ResultSet keys = ps.getGeneratedKeys(); - if (keys != null) { - - try { - - RowMapperResultSetExtractor> rse = new RowMapperResultSetExtractor<>( - new ColumnMapRowMapper(), rowsExpected); - // noinspection ConstantConditions - generatedKeys.addAll(rse.extractData(keys)); - } finally { - JdbcUtils.closeResultSet(keys); - } - } + return jdbcOperations.batchUpdate(sql, batchArgs, generatedKeyHolder, keyColumnNames); } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/InsertStrategyFactory.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/InsertStrategyFactory.java index 1133d490..3ee56848 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/InsertStrategyFactory.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/InsertStrategyFactory.java @@ -43,9 +43,9 @@ public class InsertStrategyFactory { /** * Constructor with additional {@link BatchJdbcOperations} constructor. - * - * @deprecated Use the {@link InsertStrategyFactory#InsertStrategyFactory(NamedParameterJdbcOperations, Dialect)} - * instead. + * + * @deprecated since 3.2, use + * {@link InsertStrategyFactory#InsertStrategyFactory(NamedParameterJdbcOperations, Dialect)} instead. */ @Deprecated(since = "3.2") public InsertStrategyFactory(NamedParameterJdbcOperations namedParameterJdbcOperations, diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java index b50885fa..e94127e7 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java @@ -65,6 +65,7 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.core.namedparam.SqlParameterSource; import org.springframework.jdbc.support.KeyHolder; import org.springframework.lang.Nullable; +import org.springframework.util.ObjectUtils; /** * Unit tests for application events via {@link SimpleJdbcRepository}. @@ -81,7 +82,7 @@ class SimpleJdbcRepositoryEventsUnitTests { private static final long generatedId = 4711L; - private CollectingEventPublisher publisher = new CollectingEventPublisher(); + private final CollectingEventPublisher publisher = new CollectingEventPublisher(); private DummyEntityRepository repository; private DefaultDataAccessStrategy dataAccessStrategy; @@ -306,7 +307,7 @@ class SimpleJdbcRepositoryEventsUnitTests { extends CrudRepository, PagingAndSortingRepository {} static final class DummyEntity { - @Id private final Long id; + private final @Id Long id; public DummyEntity(Long id) { this.id = id; @@ -316,34 +317,31 @@ class SimpleJdbcRepositoryEventsUnitTests { return this.id; } - public boolean equals(final Object o) { - if (o == this) - return true; - if (!(o instanceof DummyEntity)) - return false; - final DummyEntity other = (DummyEntity) o; - final Object this$id = this.getId(); - final Object other$id = other.getId(); - if (this$id == null ? other$id != null : !this$id.equals(other$id)) - return false; - return true; + public DummyEntity withId(Long id) { + return this.id == id ? this : new DummyEntity(id); } + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + DummyEntity that = (DummyEntity) o; + + return ObjectUtils.nullSafeEquals(id, that.id); + } + + @Override public int hashCode() { - final int PRIME = 59; - int result = 1; - final Object $id = this.getId(); - result = result * PRIME + ($id == null ? 43 : $id.hashCode()); - return result; + return ObjectUtils.nullSafeHashCode(id); } public String toString() { return "SimpleJdbcRepositoryEventsUnitTests.DummyEntity(id=" + this.getId() + ")"; } - public DummyEntity withId(Long id) { - return this.id == id ? this : new DummyEntity(id); - } } static class CollectingEventPublisher implements ApplicationEventPublisher { diff --git a/src/main/antora/modules/ROOT/pages/jdbc/entity-persistence.adoc b/src/main/antora/modules/ROOT/pages/jdbc/entity-persistence.adoc index 08b0420d..f94627db 100644 --- a/src/main/antora/modules/ROOT/pages/jdbc/entity-persistence.adoc +++ b/src/main/antora/modules/ROOT/pages/jdbc/entity-persistence.adoc @@ -30,13 +30,17 @@ This should be significant more efficient, especially for complex aggregates, co + Currently, Single Query Loading is restricted in different ways: -1. The aggregate must not have nested collections, this includes `Map`.The plan is to remove this constraint in the future. +1. The aggregate must not have nested collections, this includes `Map`. +The plan is to remove this constraint in the future. -2. The aggregate must not use `AggregateReference` or embedded entities.The plan is to remove this constraint in the future. +2. The aggregate must not use `AggregateReference` or embedded entities. +The plan is to remove this constraint in the future. -3. The database dialect must support it.Of the dialects provided by Spring Data JDBC all but H2 and HSQL support this.H2 and HSQL don't support analytic functions (aka windowing functions). +3. The database dialect must support it.Of the dialects provided by Spring Data JDBC all but H2 and HSQL support this. +H2 and HSQL don't support analytic functions (aka windowing functions). -4. It only works for the find methods in `CrudRepository`, not for derived queries and not for annotated queries.The plan is to remove this constraint in the future. +4. It only works for the find methods in `CrudRepository`, not for derived queries and not for annotated queries. +The plan is to remove this constraint in the future. 5. Single Query Loading needs to be enabled in the `JdbcMappingContext`, by calling `setSingleQueryLoadingEnabled(true)`