From 1d05c43debb607ddf8845f1db2a4e43bb329e372 Mon Sep 17 00:00:00 2001 From: Toshihiro Nakamura Date: Sat, 28 Sep 2024 09:24:13 +0900 Subject: [PATCH 1/3] Improve batch insertion for entities with auto-increment primary keys. Retrieve primary keys using `Statement.getGeneratedKeys()` after executing `Statement.executeBatch()`, if supported by the DBMS. --- .../doma/jdbc/command/BatchInsertCommand.java | 5 +++ .../doma/jdbc/command/BatchModifyCommand.java | 4 ++ .../org/seasar/doma/jdbc/dialect/Dialect.java | 2 + .../seasar/doma/jdbc/dialect/H2Dialect.java | 5 +++ .../doma/jdbc/dialect/MysqlDialect.java | 5 +++ .../doma/jdbc/dialect/PostgresDialect.java | 5 +++ .../jdbc/id/BuiltinIdentityIdGenerator.java | 15 +------ .../doma/jdbc/id/IdGenerationConfig.java | 1 + .../org/seasar/doma/jdbc/id/IdProvider.java | 1 + .../doma/jdbc/id/ReservedIdProvider.java | 1 + .../doma/jdbc/query/AutoBatchInsertQuery.java | 39 ++++++++++++++----- .../doma/jdbc/query/BatchInsertQuery.java | 9 +++++ .../id/BuiltinIdentityIdGeneratorTest.java | 34 ---------------- 13 files changed, 68 insertions(+), 58 deletions(-) diff --git a/doma-core/src/main/java/org/seasar/doma/jdbc/command/BatchInsertCommand.java b/doma-core/src/main/java/org/seasar/doma/jdbc/command/BatchInsertCommand.java index 3baf0079d..277d33c03 100644 --- a/doma-core/src/main/java/org/seasar/doma/jdbc/command/BatchInsertCommand.java +++ b/doma-core/src/main/java/org/seasar/doma/jdbc/command/BatchInsertCommand.java @@ -46,4 +46,9 @@ protected int executeUpdate(PreparedStatement preparedStatement, PreparedSql sql throw e; } } + + @Override + protected void postExecuteBatch(PreparedStatement preparedStatement, int position, int length) { + query.generateIds(preparedStatement, position, length); + } } diff --git a/doma-core/src/main/java/org/seasar/doma/jdbc/command/BatchModifyCommand.java b/doma-core/src/main/java/org/seasar/doma/jdbc/command/BatchModifyCommand.java index d1aaaad05..66b403267 100644 --- a/doma-core/src/main/java/org/seasar/doma/jdbc/command/BatchModifyCommand.java +++ b/doma-core/src/main/java/org/seasar/doma/jdbc/command/BatchModifyCommand.java @@ -96,6 +96,7 @@ protected int[] executeBatch(PreparedStatement preparedStatement, List 0 && (i + 1) % batchSize == 0)) { int[] rows = executeBatch(preparedStatement, sql); validateRows(preparedStatement, sql, rows); + postExecuteBatch(preparedStatement, pos, rows.length); System.arraycopy(rows, 0, updatedRows, pos, rows.length); pos = i + 1; } @@ -118,6 +119,9 @@ protected int[] executeBatch(PreparedStatement preparedStatement, PreparedSql sq } } + protected void postExecuteBatch( + PreparedStatement preparedStatement, int position, int length) {} + protected void log(PreparedSql sql) { JdbcLogger logger = query.getConfig().getJdbcLogger(); logger.logSql(query.getClassName(), query.getMethodName(), sql); diff --git a/doma-core/src/main/java/org/seasar/doma/jdbc/dialect/Dialect.java b/doma-core/src/main/java/org/seasar/doma/jdbc/dialect/Dialect.java index db3ee5741..0d8d573fd 100644 --- a/doma-core/src/main/java/org/seasar/doma/jdbc/dialect/Dialect.java +++ b/doma-core/src/main/java/org/seasar/doma/jdbc/dialect/Dialect.java @@ -129,6 +129,7 @@ default boolean supportsBatchExecutionReturningGeneratedValues() { * * @return {@code true}, if this object supports it */ + @Deprecated boolean supportsIdentityReservation(); /** @@ -218,6 +219,7 @@ Sql getIdentitySelectSql( * @throws DomaNullPointerException if either the {@code tableName} or the {@code columnName} is * {@code null} */ + @Deprecated Sql getIdentityReservationSql( String catalogName, String schemaName, diff --git a/doma-core/src/main/java/org/seasar/doma/jdbc/dialect/H2Dialect.java b/doma-core/src/main/java/org/seasar/doma/jdbc/dialect/H2Dialect.java index c1501beb2..e3bb6d1a6 100644 --- a/doma-core/src/main/java/org/seasar/doma/jdbc/dialect/H2Dialect.java +++ b/doma-core/src/main/java/org/seasar/doma/jdbc/dialect/H2Dialect.java @@ -47,6 +47,11 @@ public boolean supportsUpsertEmulationWithMergeStatement() { return true; } + @Override + public boolean supportsBatchExecutionReturningGeneratedValues() { + return true; + } + public static class H2JdbcMappingVisitor extends H214199JdbcMappingVisitor {} public static class H2SqlLogFormattingVisitor extends H214199SqlLogFormattingVisitor {} diff --git a/doma-core/src/main/java/org/seasar/doma/jdbc/dialect/MysqlDialect.java b/doma-core/src/main/java/org/seasar/doma/jdbc/dialect/MysqlDialect.java index 70aafb9aa..cee695e68 100644 --- a/doma-core/src/main/java/org/seasar/doma/jdbc/dialect/MysqlDialect.java +++ b/doma-core/src/main/java/org/seasar/doma/jdbc/dialect/MysqlDialect.java @@ -156,6 +156,11 @@ public boolean supportsAliasInDeleteClause() { return true; } + @Override + public boolean supportsBatchExecutionReturningGeneratedValues() { + return true; + } + @Override protected SqlNode toCountCalculatingSqlNode(SqlNode sqlNode) { switch (version) { diff --git a/doma-core/src/main/java/org/seasar/doma/jdbc/dialect/PostgresDialect.java b/doma-core/src/main/java/org/seasar/doma/jdbc/dialect/PostgresDialect.java index a62495731..7dc900dc6 100644 --- a/doma-core/src/main/java/org/seasar/doma/jdbc/dialect/PostgresDialect.java +++ b/doma-core/src/main/java/org/seasar/doma/jdbc/dialect/PostgresDialect.java @@ -227,6 +227,11 @@ public boolean supportsAutoGeneratedKeys() { return true; } + @Override + public boolean supportsBatchExecutionReturningGeneratedValues() { + return true; + } + @Override public JdbcType getResultSetType() { return RESULT_SET; diff --git a/doma-core/src/main/java/org/seasar/doma/jdbc/id/BuiltinIdentityIdGenerator.java b/doma-core/src/main/java/org/seasar/doma/jdbc/id/BuiltinIdentityIdGenerator.java index bd6f70297..112cbb60e 100644 --- a/doma-core/src/main/java/org/seasar/doma/jdbc/id/BuiltinIdentityIdGenerator.java +++ b/doma-core/src/main/java/org/seasar/doma/jdbc/id/BuiltinIdentityIdGenerator.java @@ -19,39 +19,26 @@ public class BuiltinIdentityIdGenerator extends AbstractIdGenerator implements I @Override public boolean supportsBatch(IdGenerationConfig config) { - return config.getIdProvider().isAvailable(); + return config.getDialect().supportsBatchExecutionReturningGeneratedValues(); } @Override public boolean includesIdentityColumn(IdGenerationConfig config) { - if (config.getIdProvider().isAvailable()) { - return true; - } return config.getDialect().includesIdentityColumn(); } @Override public boolean supportsAutoGeneratedKeys(IdGenerationConfig config) { - if (config.getIdProvider().isAvailable()) { - return false; - } return config.getDialect().supportsAutoGeneratedKeys(); } @Override public Long generatePreInsert(IdGenerationConfig config) { - IdProvider idProvider = config.getIdProvider(); - if (idProvider.isAvailable()) { - return idProvider.get(); - } return null; } @Override public Long generatePostInsert(IdGenerationConfig config, Statement statement) { - if (config.getIdProvider().isAvailable()) { - return null; - } if (config.getDialect().supportsAutoGeneratedKeys()) { return getGeneratedValue(config, statement); } diff --git a/doma-core/src/main/java/org/seasar/doma/jdbc/id/IdGenerationConfig.java b/doma-core/src/main/java/org/seasar/doma/jdbc/id/IdGenerationConfig.java index 890a20ea9..e804ede4a 100644 --- a/doma-core/src/main/java/org/seasar/doma/jdbc/id/IdGenerationConfig.java +++ b/doma-core/src/main/java/org/seasar/doma/jdbc/id/IdGenerationConfig.java @@ -74,6 +74,7 @@ public IdProvider getIdProvider() { return idProvider; } + @Deprecated protected static class UnavailableIdProvider implements IdProvider { @Override public boolean isAvailable() { diff --git a/doma-core/src/main/java/org/seasar/doma/jdbc/id/IdProvider.java b/doma-core/src/main/java/org/seasar/doma/jdbc/id/IdProvider.java index 94b4d57ec..32af67988 100644 --- a/doma-core/src/main/java/org/seasar/doma/jdbc/id/IdProvider.java +++ b/doma-core/src/main/java/org/seasar/doma/jdbc/id/IdProvider.java @@ -1,6 +1,7 @@ package org.seasar.doma.jdbc.id; /** An identity provider. */ +@Deprecated public interface IdProvider { /** diff --git a/doma-core/src/main/java/org/seasar/doma/jdbc/id/ReservedIdProvider.java b/doma-core/src/main/java/org/seasar/doma/jdbc/id/ReservedIdProvider.java index fc166a5a5..2852cf770 100644 --- a/doma-core/src/main/java/org/seasar/doma/jdbc/id/ReservedIdProvider.java +++ b/doma-core/src/main/java/org/seasar/doma/jdbc/id/ReservedIdProvider.java @@ -17,6 +17,7 @@ import org.seasar.doma.message.Message; /** An identity provider that reserves identity values in advance. */ +@Deprecated public class ReservedIdProvider implements IdProvider { protected final Config config; diff --git a/doma-core/src/main/java/org/seasar/doma/jdbc/query/AutoBatchInsertQuery.java b/doma-core/src/main/java/org/seasar/doma/jdbc/query/AutoBatchInsertQuery.java index 2c88493a7..495e291c9 100644 --- a/doma-core/src/main/java/org/seasar/doma/jdbc/query/AutoBatchInsertQuery.java +++ b/doma-core/src/main/java/org/seasar/doma/jdbc/query/AutoBatchInsertQuery.java @@ -12,6 +12,7 @@ import java.util.ListIterator; import java.util.Objects; import java.util.stream.Collectors; +import org.seasar.doma.DomaIllegalArgumentException; import org.seasar.doma.internal.jdbc.entity.AbstractPostInsertContext; import org.seasar.doma.internal.jdbc.entity.AbstractPreInsertContext; import org.seasar.doma.internal.jdbc.sql.PreparedSqlBuilder; @@ -26,7 +27,6 @@ import org.seasar.doma.jdbc.entity.GeneratedIdPropertyType; import org.seasar.doma.jdbc.entity.Property; import org.seasar.doma.jdbc.id.IdGenerationConfig; -import org.seasar.doma.jdbc.id.ReservedIdProvider; import org.seasar.doma.message.Message; public class AutoBatchInsertQuery extends AutoBatchModifyQuery @@ -106,14 +106,7 @@ protected void prepareIdAndVersionPropertyTypes() { generatedIdPropertyType = entityType.getGeneratedIdPropertyType(); if (generatedIdPropertyType != null) { if (idGenerationConfig == null) { - // TODO should we stop supporting ReservedIdProvider? - if (generatedKeysIgnored || duplicateKeyType != DuplicateKeyType.EXCEPTION) { - idGenerationConfig = new IdGenerationConfig(config, entityType); - } else { - idGenerationConfig = - new IdGenerationConfig( - config, entityType, new ReservedIdProvider(config, entityType, entities.size())); - } + idGenerationConfig = new IdGenerationConfig(config, entityType); generatedIdPropertyType.validateGenerationStrategy(idGenerationConfig); autoGeneratedKeysSupported = !generatedKeysIgnored @@ -233,7 +226,7 @@ public boolean isBatchSupported() { @Override public void generateId(Statement statement, int index) { - if (generatedIdPropertyType != null && idGenerationConfig != null) { + if (isAutoGeneratedKeysSupported()) { ENTITY entity = entities.get(index); ENTITY newEntity = generatedIdPropertyType @@ -246,6 +239,32 @@ public void generateId(Statement statement, int index) { } } + @Override + public void generateIds(Statement statement, int position, int size) { + if (isAutoGeneratedKeysSupported() && isBatchSupported()) { + if ((position + size) > entities.size()) { + throw new DomaIllegalArgumentException( + "position or length", + "position = " + + position + + ", size = " + + size + + ", entities.size() = " + + entities.size()); + } + List subEntities = entities.subList(position, position + size); + List newEntities = + generatedIdPropertyType + .postInsert(entityType, subEntities, idGenerationConfig, statement) + .stream() + .toList(); + if (subEntities.size() == newEntities.size()) { + subEntities.clear(); + subEntities.addAll(newEntities); + } + } + } + @Override public void complete() { for (ListIterator it = entities.listIterator(); it.hasNext(); ) { diff --git a/doma-core/src/main/java/org/seasar/doma/jdbc/query/BatchInsertQuery.java b/doma-core/src/main/java/org/seasar/doma/jdbc/query/BatchInsertQuery.java index 8677cde8f..79fe3b291 100644 --- a/doma-core/src/main/java/org/seasar/doma/jdbc/query/BatchInsertQuery.java +++ b/doma-core/src/main/java/org/seasar/doma/jdbc/query/BatchInsertQuery.java @@ -7,4 +7,13 @@ public interface BatchInsertQuery extends BatchModifyQuery { boolean isBatchSupported(); void generateId(Statement statement, int index); + + /** + * Generates IDs for the batch. + * + * @param statement the statement + * @param position the position of the first element in the batch + * @param size the size of the executed batch + */ + default void generateIds(Statement statement, int position, int size) {} } diff --git a/doma-core/src/test/java/org/seasar/doma/jdbc/id/BuiltinIdentityIdGeneratorTest.java b/doma-core/src/test/java/org/seasar/doma/jdbc/id/BuiltinIdentityIdGeneratorTest.java index 3ec77e8fb..b992827c4 100644 --- a/doma-core/src/test/java/org/seasar/doma/jdbc/id/BuiltinIdentityIdGeneratorTest.java +++ b/doma-core/src/test/java/org/seasar/doma/jdbc/id/BuiltinIdentityIdGeneratorTest.java @@ -1,16 +1,13 @@ package org.seasar.doma.jdbc.id; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; -import example.entity.IdGeneratedEmp; import example.entity._IdGeneratedEmp; import org.junit.jupiter.api.Test; import org.seasar.doma.internal.jdbc.mock.MockConfig; import org.seasar.doma.internal.jdbc.mock.MockResultSet; import org.seasar.doma.internal.jdbc.mock.RowData; import org.seasar.doma.jdbc.dialect.PostgresDialect; -import org.seasar.doma.jdbc.entity.EntityType; public class BuiltinIdentityIdGeneratorTest { @@ -38,35 +35,4 @@ public boolean supportsAutoGeneratedKeys() { "select currval(pg_catalog.pg_get_serial_sequence('\"CATA\".\"EMP\"', 'id'))", config.dataSource.connection.preparedStatement.sql); } - - @Test - public void test_identityReservationSql() { - MockConfig config = new MockConfig(); - config.setDialect(new PostgresDialect()); - MockResultSet resultSet = config.dataSource.connection.preparedStatement.resultSet; - resultSet.rows.add(new RowData(11L)); - resultSet.rows.add(new RowData(12L)); - resultSet.rows.add(new RowData(13L)); - - EntityType entityType = _IdGeneratedEmp.getSingletonInternal(); - BuiltinIdentityIdGenerator identityIdGenerator = new BuiltinIdentityIdGenerator(); - IdGenerationConfig idGenerationConfig = - new IdGenerationConfig(config, entityType, new ReservedIdProvider(config, entityType, 3)); - Long value = identityIdGenerator.generatePreInsert(idGenerationConfig); - assertEquals(11L, value); - assertEquals( - "select nextval(pg_catalog.pg_get_serial_sequence('\"CATA\".\"EMP\"', 'id')) from generate_series(1, 3)", - config.dataSource.connection.preparedStatement.sql); - value = identityIdGenerator.generatePreInsert(idGenerationConfig); - assertEquals(12L, value); - value = identityIdGenerator.generatePreInsert(idGenerationConfig); - assertEquals(13L, value); - - try { - identityIdGenerator.generatePreInsert(idGenerationConfig); - fail(); - } catch (IllegalStateException e) { - System.out.println(e.getMessage()); - } - } } From 8067e7bb5e38e361eeb0d84798c6b3226b9536cd Mon Sep 17 00:00:00 2001 From: Toshihiro Nakamura Date: Sat, 28 Sep 2024 09:52:56 +0900 Subject: [PATCH 2/3] Format and add comments --- .../seasar/doma/jdbc/command/BatchInsertCommand.java | 4 ++-- .../seasar/doma/jdbc/command/BatchModifyCommand.java | 10 ++++++++-- .../org/seasar/doma/jdbc/query/BatchInsertQuery.java | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/doma-core/src/main/java/org/seasar/doma/jdbc/command/BatchInsertCommand.java b/doma-core/src/main/java/org/seasar/doma/jdbc/command/BatchInsertCommand.java index 277d33c03..8ae846ad1 100644 --- a/doma-core/src/main/java/org/seasar/doma/jdbc/command/BatchInsertCommand.java +++ b/doma-core/src/main/java/org/seasar/doma/jdbc/command/BatchInsertCommand.java @@ -48,7 +48,7 @@ protected int executeUpdate(PreparedStatement preparedStatement, PreparedSql sql } @Override - protected void postExecuteBatch(PreparedStatement preparedStatement, int position, int length) { - query.generateIds(preparedStatement, position, length); + protected void postExecuteBatch(PreparedStatement preparedStatement, int position, int size) { + query.generateIds(preparedStatement, position, size); } } diff --git a/doma-core/src/main/java/org/seasar/doma/jdbc/command/BatchModifyCommand.java b/doma-core/src/main/java/org/seasar/doma/jdbc/command/BatchModifyCommand.java index 66b403267..c837415f5 100644 --- a/doma-core/src/main/java/org/seasar/doma/jdbc/command/BatchModifyCommand.java +++ b/doma-core/src/main/java/org/seasar/doma/jdbc/command/BatchModifyCommand.java @@ -119,8 +119,14 @@ protected int[] executeBatch(PreparedStatement preparedStatement, PreparedSql sq } } - protected void postExecuteBatch( - PreparedStatement preparedStatement, int position, int length) {} + /** + * Invoked after the batch execution. + * + * @param preparedStatement the prepared statement + * @param position the position of the first element in the batch + * @param size the size of the executed batch + */ + protected void postExecuteBatch(PreparedStatement preparedStatement, int position, int size) {} protected void log(PreparedSql sql) { JdbcLogger logger = query.getConfig().getJdbcLogger(); diff --git a/doma-core/src/main/java/org/seasar/doma/jdbc/query/BatchInsertQuery.java b/doma-core/src/main/java/org/seasar/doma/jdbc/query/BatchInsertQuery.java index 79fe3b291..77ee6b4a3 100644 --- a/doma-core/src/main/java/org/seasar/doma/jdbc/query/BatchInsertQuery.java +++ b/doma-core/src/main/java/org/seasar/doma/jdbc/query/BatchInsertQuery.java @@ -10,7 +10,7 @@ public interface BatchInsertQuery extends BatchModifyQuery { /** * Generates IDs for the batch. - * + * * @param statement the statement * @param position the position of the first element in the batch * @param size the size of the executed batch From 2d570fb7ad39813be710c36c55941dd4979700c5 Mon Sep 17 00:00:00 2001 From: Toshihiro Nakamura Date: Sat, 28 Sep 2024 10:00:55 +0900 Subject: [PATCH 3/3] Suppress warnings --- .../java/org/seasar/doma/jdbc/dialect/StandardDialect.java | 2 ++ .../main/java/org/seasar/doma/jdbc/id/IdGenerationConfig.java | 3 +++ 2 files changed, 5 insertions(+) diff --git a/doma-core/src/main/java/org/seasar/doma/jdbc/dialect/StandardDialect.java b/doma-core/src/main/java/org/seasar/doma/jdbc/dialect/StandardDialect.java index 026c5c8f5..3d4fad456 100644 --- a/doma-core/src/main/java/org/seasar/doma/jdbc/dialect/StandardDialect.java +++ b/doma-core/src/main/java/org/seasar/doma/jdbc/dialect/StandardDialect.java @@ -306,6 +306,7 @@ public boolean supportsSequence() { return false; } + @SuppressWarnings("deprecation") @Override public boolean supportsIdentityReservation() { return false; @@ -342,6 +343,7 @@ public Sql getIdentitySelectSql( throw new JdbcUnsupportedOperationException(getClass().getName(), "getIdentitySelectSql"); } + @SuppressWarnings("deprecation") @Override public Sql getIdentityReservationSql( String catalogName, diff --git a/doma-core/src/main/java/org/seasar/doma/jdbc/id/IdGenerationConfig.java b/doma-core/src/main/java/org/seasar/doma/jdbc/id/IdGenerationConfig.java index e804ede4a..4deb10afa 100644 --- a/doma-core/src/main/java/org/seasar/doma/jdbc/id/IdGenerationConfig.java +++ b/doma-core/src/main/java/org/seasar/doma/jdbc/id/IdGenerationConfig.java @@ -17,6 +17,7 @@ public class IdGenerationConfig { protected final EntityType entityType; + @SuppressWarnings("deprecation") protected final IdProvider idProvider; public IdGenerationConfig(Config config, EntityType entityType) { @@ -70,10 +71,12 @@ public EntityType getEntityType() { return entityType; } + @SuppressWarnings("deprecation") public IdProvider getIdProvider() { return idProvider; } + @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated protected static class UnavailableIdProvider implements IdProvider { @Override