diff --git a/CHANGELOG.md b/CHANGELOG.md index 4993b25..696f1c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [PR #44](https://github.com/itsallcode/simple-jdbc/pull/44): Add `GenericDialect` for unsupported databases - [PR #45](https://github.com/itsallcode/simple-jdbc/pull/45): Rename `executeStatement()` to `executeUpdate()` and return row count (**Breaking change**) - [PR #46](https://github.com/itsallcode/simple-jdbc/pull/46): Close `Statement` / `PreparedStatement` when closing the result set. +- [PR #47](https://github.com/itsallcode/simple-jdbc/pull/47): Rename `BatchInsert` to `PreparedStatementBatch`, allow specifying a custom SQL statement (**Breaking change**) ## [0.9.0] - 2024-12-23 diff --git a/README.md b/README.md index 05940db..56b6c2b 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ See complete example code in [ExampleTest](src/test/java/org/itsallcode/jdbc/exa import org.itsallcode.jdbc.ConnectionFactory; import org.itsallcode.jdbc.SimpleConnection; import org.itsallcode.jdbc.Transaction; -import org.itsallcode.jdbc.resultset.batch.BatchInsert; +import org.itsallcode.jdbc.resultset.batch.StatementBatch; import org.itsallcode.jdbc.resultset.SimpleResultSet; import org.itsallcode.jdbc.resultset.generic.Row; ``` @@ -93,7 +93,7 @@ connection.batchInsert(Name.class) This allows using batch inserts without creating objects for each row to avoid memory allocations. ```java -try (BatchInsert batch = transaction.batchInsert().into("NAMES", List.of("ID", "NAME")).build()) { +try (PreparedStatementBatch batch = transaction.preparedStatementBatch().into("NAMES", List.of("ID", "NAME")).build()) { for (int i = 0; i < 5; i++) { final int id = i + 1; batch.add(ps -> { diff --git a/src/integrationTest/java/org/itsallcode/jdbc/BatchInsertPerformanceTest.java b/src/integrationTest/java/org/itsallcode/jdbc/BatchInsertPerformanceTest.java index 5f6405f..6702023 100644 --- a/src/integrationTest/java/org/itsallcode/jdbc/BatchInsertPerformanceTest.java +++ b/src/integrationTest/java/org/itsallcode/jdbc/BatchInsertPerformanceTest.java @@ -19,7 +19,7 @@ @ExtendWith(MockitoExtension.class) class BatchInsertPerformanceTest { private static final int ROW_COUNT = 10_000_000; - private static final int MAX_BATCH_SIZE = BatchInsertBuilder.DEFAULT_MAX_BATCH_SIZE; + private static final int MAX_BATCH_SIZE = PreparedStatementBatchBuilder.DEFAULT_MAX_BATCH_SIZE; @Mock SimpleConnection connectionMock; @Mock @@ -35,12 +35,12 @@ void rowStatementSetter() { }).rows(generateStream(ROW_COUNT)).start(); } - private RowBatchInsertBuilder rowTestee() { - return new RowBatchInsertBuilder(this::prepareStatement).maxBatchSize(MAX_BATCH_SIZE); + private RowPreparedStatementBatchBuilder rowTestee() { + return new RowPreparedStatementBatchBuilder(this::prepareStatement).maxBatchSize(MAX_BATCH_SIZE); } - private BatchInsertBuilder testee() { - return new BatchInsertBuilder(this::prepareStatement).maxBatchSize(MAX_BATCH_SIZE); + private PreparedStatementBatchBuilder testee() { + return new PreparedStatementBatchBuilder(this::prepareStatement).maxBatchSize(MAX_BATCH_SIZE); } private SimplePreparedStatement prepareStatement(final String sql) { @@ -75,7 +75,7 @@ void objectArray() { @Test @Timeout(value = 10, unit = TimeUnit.SECONDS) void directAdd() { - try (BatchInsert batch = testee().into("TEST", List.of("ID", "NAME")).build()) { + try (PreparedStatementBatch batch = testee().into("TEST", List.of("ID", "NAME")).build()) { for (int i = 0; i < ROW_COUNT; i++) { final int row = i; batch.add(ps -> { @@ -89,7 +89,7 @@ void directAdd() { @Test @Timeout(value = 10, unit = TimeUnit.SECONDS) void directAddBatch() throws SQLException { - try (BatchInsert batch = testee().into("TEST", List.of("ID", "NAME")).build()) { + try (PreparedStatementBatch batch = testee().into("TEST", List.of("ID", "NAME")).build()) { final PreparedStatement statement = batch.getStatement(); for (int i = 0; i < ROW_COUNT; i++) { statement.setInt(1, i); diff --git a/src/integrationTest/java/org/itsallcode/jdbc/ExasolTypeTest.java b/src/integrationTest/java/org/itsallcode/jdbc/ExasolTypeTest.java index 5c680f2..dee131a 100644 --- a/src/integrationTest/java/org/itsallcode/jdbc/ExasolTypeTest.java +++ b/src/integrationTest/java/org/itsallcode/jdbc/ExasolTypeTest.java @@ -182,7 +182,7 @@ void batchInsert() { try (final SimpleConnection connection = connect()) { connection.executeUpdate("create schema test"); connection.executeUpdate("create table tab(col date)"); - connection.batchInsert(LocalDate.class).into("TAB", List.of("COL")) + connection.preparedStatementBatch(LocalDate.class).into("TAB", List.of("COL")) .mapping((row, stmt) -> stmt.setObject(1, row)).rows(Stream.of(date)).start(); try (SimpleResultSet resultSet = connection.query("select * from tab", (rs, rowNum) -> rs.getObject(1, LocalDate.class))) { diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 0e77de5..90fb6ee 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -21,7 +21,7 @@ *
  • ... with a prepared statement and custom parameter setter: * {@link org.itsallcode.jdbc.DbOperations#executeUpdate(String, org.itsallcode.jdbc.PreparedStatementSetter)}
  • *
  • ... multiple statements in a batch: - * {@link org.itsallcode.jdbc.DbOperations#batch()}
  • + * {@link org.itsallcode.jdbc.DbOperations#statementBatch()} * *
  • ... semicolon separated SQL script: * {@link org.itsallcode.jdbc.DbOperations#executeScript(String)}
  • @@ -57,9 +57,9 @@ *
      *
    • ... using a {@link java.util.stream.Stream} or {@link java.util.Iterator} * of row objects: - * {@link org.itsallcode.jdbc.SimpleConnection#batchInsert(java.lang.Class)}
    • + * {@link org.itsallcode.jdbc.SimpleConnection#preparedStatementBatch(java.lang.Class)} *
    • ... directly setting values of a {@link java.sql.PreparedStatement}: - * {@link org.itsallcode.jdbc.SimpleConnection#batchInsert()}
    • + * {@link org.itsallcode.jdbc.SimpleConnection#preparedStatementBatch()} *
    * *
  • Simplified Exception Handling: converts checked exception diff --git a/src/main/java/org/itsallcode/jdbc/ConnectionWrapper.java b/src/main/java/org/itsallcode/jdbc/ConnectionWrapper.java index 14f6c9d..342cbf9 100644 --- a/src/main/java/org/itsallcode/jdbc/ConnectionWrapper.java +++ b/src/main/java/org/itsallcode/jdbc/ConnectionWrapper.java @@ -53,7 +53,7 @@ void executeScript(final String sqlScript) { if (statements.isEmpty()) { return; } - try (StatementBatch batch = this.batch().build()) { + try (StatementBatch batch = this.statementBatch().build()) { statements.forEach(batch::addBatch); } } @@ -77,7 +77,7 @@ private SimplePreparedStatement prepareStatement(final String sql) { new ConvertingPreparedStatement(prepare(sql), paramSetterProvider), sql); } - StatementBatchBuilder batch() { + StatementBatchBuilder statementBatch() { return new StatementBatchBuilder(this::createSimpleStatement); } @@ -85,12 +85,12 @@ private SimpleStatement createSimpleStatement() { return new SimpleStatement(context, dialect, createStatement()); } - BatchInsertBuilder batchInsert() { - return new BatchInsertBuilder(this::prepareStatement); + PreparedStatementBatchBuilder preparedStatementBatch() { + return new PreparedStatementBatchBuilder(this::prepareStatement); } - RowBatchInsertBuilder rowBatchInsert() { - return new RowBatchInsertBuilder<>(this::prepareStatement); + RowPreparedStatementBatchBuilder rowPreparedStatementBatch() { + return new RowPreparedStatementBatchBuilder<>(this::prepareStatement); } private PreparedStatement prepare(final String sql) { diff --git a/src/main/java/org/itsallcode/jdbc/DbOperations.java b/src/main/java/org/itsallcode/jdbc/DbOperations.java index f1f0db7..8a45361 100644 --- a/src/main/java/org/itsallcode/jdbc/DbOperations.java +++ b/src/main/java/org/itsallcode/jdbc/DbOperations.java @@ -120,31 +120,31 @@ SimpleResultSet query(final String sql, final PreparedStatementSetter pre * * @return batch statement builder */ - StatementBatchBuilder batch(); + StatementBatchBuilder statementBatch(); /** - * Create a batch insert builder for inserting rows by directly setting values - * of a {@link PreparedStatement}. + * Create a prepared statement batch builder for inserting or updating rows by + * directly setting values of a {@link PreparedStatement}. *

    * If you want to insert rows from an {@link Iterator} or a {@link Stream}, use - * {@link #batchInsert(Class)}. + * {@link #preparedStatementBatch(Class)}. * * @return batch insert builder */ - BatchInsertBuilder batchInsert(); + PreparedStatementBatchBuilder preparedStatementBatch(); /** - * Create a row-based batch insert builder for inserting rows from an - * {@link Iterator} or a {@link Stream}. + * Create a row-based prepared statement batch builder for inserting or updating + * rows from an {@link Iterator} or a {@link Stream}. *

    * If you want to insert rows by directly setting values of a - * {@link PreparedStatement}, use {@link #batchInsert()}. + * {@link PreparedStatement}, use {@link #preparedStatementBatch()}. * * @param rowType row type * @param row type * @return row-based batch insert builder */ - RowBatchInsertBuilder batchInsert(final Class rowType); + RowPreparedStatementBatchBuilder preparedStatementBatch(final Class rowType); /** * Get the original wrapped connection. diff --git a/src/main/java/org/itsallcode/jdbc/SimpleConnection.java b/src/main/java/org/itsallcode/jdbc/SimpleConnection.java index 7718b02..cead7f9 100644 --- a/src/main/java/org/itsallcode/jdbc/SimpleConnection.java +++ b/src/main/java/org/itsallcode/jdbc/SimpleConnection.java @@ -100,21 +100,21 @@ public SimpleResultSet query(final String sql, final PreparedStatementSet } @Override - public StatementBatchBuilder batch() { + public StatementBatchBuilder statementBatch() { checkOperationAllowed(); - return connection.batch(); + return connection.statementBatch(); } @Override - public BatchInsertBuilder batchInsert() { + public PreparedStatementBatchBuilder preparedStatementBatch() { checkOperationAllowed(); - return connection.batchInsert(); + return connection.preparedStatementBatch(); } @Override - public RowBatchInsertBuilder batchInsert(final Class rowType) { + public RowPreparedStatementBatchBuilder preparedStatementBatch(final Class rowType) { checkOperationAllowed(); - return connection.rowBatchInsert(); + return connection.rowPreparedStatementBatch(); } public Connection getOriginalConnection() { diff --git a/src/main/java/org/itsallcode/jdbc/Transaction.java b/src/main/java/org/itsallcode/jdbc/Transaction.java index 4b44dfb..d6aeb9f 100644 --- a/src/main/java/org/itsallcode/jdbc/Transaction.java +++ b/src/main/java/org/itsallcode/jdbc/Transaction.java @@ -105,21 +105,21 @@ public SimpleResultSet query(final String sql, final PreparedStatementSet } @Override - public StatementBatchBuilder batch() { + public StatementBatchBuilder statementBatch() { checkOperationAllowed(); - return connection.batch(); + return connection.statementBatch(); } @Override - public BatchInsertBuilder batchInsert() { + public PreparedStatementBatchBuilder preparedStatementBatch() { checkOperationAllowed(); - return connection.batchInsert(); + return connection.preparedStatementBatch(); } @Override - public RowBatchInsertBuilder batchInsert(final Class rowType) { + public RowPreparedStatementBatchBuilder preparedStatementBatch(final Class rowType) { checkOperationAllowed(); - return connection.rowBatchInsert(); + return connection.rowPreparedStatementBatch(); } public Connection getOriginalConnection() { diff --git a/src/main/java/org/itsallcode/jdbc/batch/BatchInsert.java b/src/main/java/org/itsallcode/jdbc/batch/PreparedStatementBatch.java similarity index 90% rename from src/main/java/org/itsallcode/jdbc/batch/BatchInsert.java rename to src/main/java/org/itsallcode/jdbc/batch/PreparedStatementBatch.java index 08ff528..acbc525 100644 --- a/src/main/java/org/itsallcode/jdbc/batch/BatchInsert.java +++ b/src/main/java/org/itsallcode/jdbc/batch/PreparedStatementBatch.java @@ -7,13 +7,13 @@ /** * Direct batch insert using {@link PreparedStatement}. Create a new instance - * using {@link SimpleConnection#batchInsert()}. + * using {@link SimpleConnection#preparedStatementBatch()}. */ -public class BatchInsert implements AutoCloseable { +public class PreparedStatementBatch implements AutoCloseable { private final Batch batch; private final SimplePreparedStatement statement; - BatchInsert(final SimplePreparedStatement statement, final int maxBatchSize) { + PreparedStatementBatch(final SimplePreparedStatement statement, final int maxBatchSize) { this.statement = Objects.requireNonNull(statement, "statement"); this.batch = new Batch(maxBatchSize, statement, statement::executeBatch); } diff --git a/src/main/java/org/itsallcode/jdbc/batch/BatchInsertBuilder.java b/src/main/java/org/itsallcode/jdbc/batch/PreparedStatementBatchBuilder.java similarity index 70% rename from src/main/java/org/itsallcode/jdbc/batch/BatchInsertBuilder.java rename to src/main/java/org/itsallcode/jdbc/batch/PreparedStatementBatchBuilder.java index d714ac2..de7c187 100644 --- a/src/main/java/org/itsallcode/jdbc/batch/BatchInsertBuilder.java +++ b/src/main/java/org/itsallcode/jdbc/batch/PreparedStatementBatchBuilder.java @@ -12,11 +12,11 @@ import org.itsallcode.jdbc.identifier.Identifier; /** - * Builder for {@link BatchInsert}. Create a new builder instance using - * {@link SimpleConnection#batchInsert()}. + * Builder for {@link PreparedStatementBatch}. Create a new builder instance + * using {@link SimpleConnection#preparedStatementBatch()}. */ -public class BatchInsertBuilder { - private static final Logger LOG = Logger.getLogger(BatchInsertBuilder.class.getName()); +public class PreparedStatementBatchBuilder { + private static final Logger LOG = Logger.getLogger(PreparedStatementBatchBuilder.class.getName()); /** Default maximum batch size. */ public static final int DEFAULT_MAX_BATCH_SIZE = 200_000; private final Function statementFactory; @@ -28,10 +28,22 @@ public class BatchInsertBuilder { * * @param statementFactory factory for creating {@link SimplePreparedStatement}. */ - public BatchInsertBuilder(final Function statementFactory) { + public PreparedStatementBatchBuilder(final Function statementFactory) { this.statementFactory = statementFactory; } + /** + * Define the SQL statement to be used for the batch job, e.g. {@code INSERT} or + * {@code UPDATE}. + * + * @param sql SQL statement + * @return {@code this} for fluent programming + */ + public PreparedStatementBatchBuilder sql(final String sql) { + this.sql = sql; + return this; + } + /** * Define table and column names used for generating the {@code INSERT} * statement. @@ -41,7 +53,7 @@ public BatchInsertBuilder(final Function statem * @return {@code this} for fluent programming */ @SuppressWarnings("java:S3242") // Using List instead of Collection to preserve column order - public BatchInsertBuilder into(final Identifier tableName, final List columnNames) { + public PreparedStatementBatchBuilder into(final Identifier tableName, final List columnNames) { this.sql = createInsertStatement(tableName, columnNames); return this; } @@ -55,7 +67,7 @@ public BatchInsertBuilder into(final Identifier tableName, final List columnNames) { + public PreparedStatementBatchBuilder into(final String tableName, final List columnNames) { return into(Identifier.simple(tableName), columnNames.stream().map(Identifier::simple).toList()); } @@ -65,7 +77,7 @@ public BatchInsertBuilder into(final String tableName, final List column * @param maxBatchSize maximum batch size * @return {@code this} for fluent programming */ - public BatchInsertBuilder maxBatchSize(final int maxBatchSize) { + public PreparedStatementBatchBuilder maxBatchSize(final int maxBatchSize) { this.maxBatchSize = maxBatchSize; return this; } @@ -81,10 +93,10 @@ private static String createInsertStatement(final Identifier table, final List "Running insert statement '" + sql + "'..."); final SimplePreparedStatement statement = statementFactory.apply(sql); - return new BatchInsert(statement, this.maxBatchSize); + return new PreparedStatementBatch(statement, this.maxBatchSize); } } diff --git a/src/main/java/org/itsallcode/jdbc/batch/RowBatchInsert.java b/src/main/java/org/itsallcode/jdbc/batch/RowPreparedStatementBatch.java similarity index 53% rename from src/main/java/org/itsallcode/jdbc/batch/RowBatchInsert.java rename to src/main/java/org/itsallcode/jdbc/batch/RowPreparedStatementBatch.java index ea6d103..884cb0b 100644 --- a/src/main/java/org/itsallcode/jdbc/batch/RowBatchInsert.java +++ b/src/main/java/org/itsallcode/jdbc/batch/RowPreparedStatementBatch.java @@ -3,17 +3,19 @@ import org.itsallcode.jdbc.RowPreparedStatementSetter; import org.itsallcode.jdbc.SimplePreparedStatement; -class RowBatchInsert implements AutoCloseable { +class RowPreparedStatementBatch implements AutoCloseable { - private final BatchInsert batchInsert; + private final PreparedStatementBatch batchInsert; private final RowPreparedStatementSetter preparedStatementSetter; - RowBatchInsert(final SimplePreparedStatement statement, final RowPreparedStatementSetter preparedStatementSetter, + RowPreparedStatementBatch(final SimplePreparedStatement statement, + final RowPreparedStatementSetter preparedStatementSetter, final int maxBatchSize) { - this(new BatchInsert(statement, maxBatchSize), preparedStatementSetter); + this(new PreparedStatementBatch(statement, maxBatchSize), preparedStatementSetter); } - RowBatchInsert(final BatchInsert batchInsert, final RowPreparedStatementSetter preparedStatementSetter) { + RowPreparedStatementBatch(final PreparedStatementBatch batchInsert, + final RowPreparedStatementSetter preparedStatementSetter) { this.batchInsert = batchInsert; this.preparedStatementSetter = preparedStatementSetter; } diff --git a/src/main/java/org/itsallcode/jdbc/batch/RowBatchInsertBuilder.java b/src/main/java/org/itsallcode/jdbc/batch/RowPreparedStatementBatchBuilder.java similarity index 62% rename from src/main/java/org/itsallcode/jdbc/batch/RowBatchInsertBuilder.java rename to src/main/java/org/itsallcode/jdbc/batch/RowPreparedStatementBatchBuilder.java index 0bec1de..e694c72 100644 --- a/src/main/java/org/itsallcode/jdbc/batch/RowBatchInsertBuilder.java +++ b/src/main/java/org/itsallcode/jdbc/batch/RowPreparedStatementBatchBuilder.java @@ -9,12 +9,17 @@ import org.itsallcode.jdbc.identifier.Identifier; /** - * Builder for batch inserts. + * Builder for {@link PreparedStatement} batch jobs for a {@link Stream} or + * {@link Iterable} of row objects using e.g. {@code INSERT} or {@code UPDATE} + * statements. + *

    + * Create a new instance using + * {@link DbOperations#preparedStatementBatch(Class)}. * * @param row type */ -public class RowBatchInsertBuilder { - private final BatchInsertBuilder baseBuilder; +public class RowPreparedStatementBatchBuilder { + private final PreparedStatementBatchBuilder baseBuilder; private RowPreparedStatementSetter mapper; private Iterator rows; @@ -23,14 +28,26 @@ public class RowBatchInsertBuilder { * * @param statementFactory factory for creating {@link SimplePreparedStatement} */ - public RowBatchInsertBuilder(final Function statementFactory) { - this(new BatchInsertBuilder(statementFactory)); + public RowPreparedStatementBatchBuilder(final Function statementFactory) { + this(new PreparedStatementBatchBuilder(statementFactory)); } - RowBatchInsertBuilder(final BatchInsertBuilder baseBuilder) { + RowPreparedStatementBatchBuilder(final PreparedStatementBatchBuilder baseBuilder) { this.baseBuilder = baseBuilder; } + /** + * Define the SQL statement to be used for the batch job, e.g. {@code INSERT} or + * {@code UPDATE}. + * + * @param sql SQL statement + * @return {@code this} for fluent programming + */ + public RowPreparedStatementBatchBuilder sql(final String sql) { + this.baseBuilder.sql(sql); + return this; + } + /** * Define table and column names used for generating the {@code INSERT} * statement. @@ -40,7 +57,7 @@ public RowBatchInsertBuilder(final Function sta * @return {@code this} for fluent programming */ @SuppressWarnings("java:S3242") // Using List instead of Collection to preserve column order - public RowBatchInsertBuilder into(final Identifier tableName, final List columnNames) { + public RowPreparedStatementBatchBuilder into(final Identifier tableName, final List columnNames) { this.baseBuilder.into(tableName, columnNames); return this; } @@ -54,7 +71,7 @@ public RowBatchInsertBuilder into(final Identifier tableName, final List into(final String tableName, final List columnNames) { + public RowPreparedStatementBatchBuilder into(final String tableName, final List columnNames) { return into(Identifier.simple(tableName), columnNames.stream().map(Identifier::simple).toList()); } @@ -64,7 +81,7 @@ public RowBatchInsertBuilder into(final String tableName, final List * @param rows rows to insert * @return {@code this} for fluent programming */ - public RowBatchInsertBuilder rows(final Stream rows) { + public RowPreparedStatementBatchBuilder rows(final Stream rows) { return rows(rows.iterator()); } @@ -74,7 +91,7 @@ public RowBatchInsertBuilder rows(final Stream rows) { * @param rows rows to insert * @return {@code this} for fluent programming */ - public RowBatchInsertBuilder rows(final Iterator rows) { + public RowPreparedStatementBatchBuilder rows(final Iterator rows) { this.rows = rows; return this; } @@ -85,7 +102,7 @@ public RowBatchInsertBuilder rows(final Iterator rows) { * @param rowMapper row mapper * @return {@code this} for fluent programming */ - public RowBatchInsertBuilder mapping(final ParamConverter rowMapper) { + public RowPreparedStatementBatchBuilder mapping(final ParamConverter rowMapper) { final RowPreparedStatementSetter setter = new ObjectArrayPreparedStatementSetter(); return mapping( (final T row, final PreparedStatement preparedStatement) -> setter.setValues(rowMapper.map(row), @@ -99,19 +116,19 @@ public RowBatchInsertBuilder mapping(final ParamConverter rowMapper) { * @param preparedStatementSetter prepared statement setter * @return {@code this} for fluent programming */ - public RowBatchInsertBuilder mapping(final RowPreparedStatementSetter preparedStatementSetter) { + public RowPreparedStatementBatchBuilder mapping(final RowPreparedStatementSetter preparedStatementSetter) { this.mapper = preparedStatementSetter; return this; } /** * Define maximum batch size, using - * {@link BatchInsertBuilder#DEFAULT_MAX_BATCH_SIZE} as default. + * {@link PreparedStatementBatchBuilder#DEFAULT_MAX_BATCH_SIZE} as default. * * @param maxBatchSize maximum batch size * @return {@code this} for fluent programming */ - public RowBatchInsertBuilder maxBatchSize(final int maxBatchSize) { + public RowPreparedStatementBatchBuilder maxBatchSize(final int maxBatchSize) { this.baseBuilder.maxBatchSize(maxBatchSize); return this; } @@ -122,8 +139,9 @@ public RowBatchInsertBuilder maxBatchSize(final int maxBatchSize) { public void start() { Objects.requireNonNull(this.mapper, "mapper"); Objects.requireNonNull(this.rows, "rows"); - try (BatchInsert batchInsert = baseBuilder.build(); - RowBatchInsert rowBatchInsert = new RowBatchInsert<>(batchInsert, this.mapper)) { + try (PreparedStatementBatch batchInsert = baseBuilder.build(); + RowPreparedStatementBatch rowBatchInsert = new RowPreparedStatementBatch<>(batchInsert, + this.mapper)) { while (rows.hasNext()) { rowBatchInsert.add(rows.next()); } diff --git a/src/main/java/org/itsallcode/jdbc/batch/StatementBatchBuilder.java b/src/main/java/org/itsallcode/jdbc/batch/StatementBatchBuilder.java index 66c9f55..098182e 100644 --- a/src/main/java/org/itsallcode/jdbc/batch/StatementBatchBuilder.java +++ b/src/main/java/org/itsallcode/jdbc/batch/StatementBatchBuilder.java @@ -9,7 +9,7 @@ */ public class StatementBatchBuilder { private final Supplier statementFactory; - private int maxBatchSize = BatchInsertBuilder.DEFAULT_MAX_BATCH_SIZE; + private int maxBatchSize = PreparedStatementBatchBuilder.DEFAULT_MAX_BATCH_SIZE; /** * Create a new instance. @@ -22,7 +22,7 @@ public StatementBatchBuilder(final Supplier statementFactory) { /** * Define maximum batch size, using - * {@link BatchInsertBuilder#DEFAULT_MAX_BATCH_SIZE} as default. + * {@link PreparedStatementBatchBuilder#DEFAULT_MAX_BATCH_SIZE} as default. * * @param maxBatchSize maximum batch size * @return {@code this} for fluent programming diff --git a/src/test/java/org/itsallcode/jdbc/ConnectionWrapperTest.java b/src/test/java/org/itsallcode/jdbc/ConnectionWrapperTest.java index caa1152..747a49e 100644 --- a/src/test/java/org/itsallcode/jdbc/ConnectionWrapperTest.java +++ b/src/test/java/org/itsallcode/jdbc/ConnectionWrapperTest.java @@ -8,7 +8,7 @@ import java.util.List; import java.util.stream.Stream; -import org.itsallcode.jdbc.batch.BatchInsert; +import org.itsallcode.jdbc.batch.PreparedStatementBatch; import org.itsallcode.jdbc.dialect.GenericDialect; import org.itsallcode.jdbc.resultset.ContextRowMapper; import org.itsallcode.jdbc.resultset.SimpleResultSet; @@ -127,7 +127,9 @@ void query() throws SQLException { void batchInsert() throws SQLException { when(connectionMock.prepareStatement("insert into \"tab\" (\"c1\",\"c2\") values (?,?)")) .thenReturn(preparedStatementMock); - final BatchInsert batch = testee().batchInsert().into("tab", List.of("c1", "c2")).maxBatchSize(4).build(); + final PreparedStatementBatch batch = testee().preparedStatementBatch().into("tab", List.of("c1", "c2")) + .maxBatchSize(4) + .build(); batch.add(ps -> { ps.setString(1, "one"); }); @@ -145,7 +147,7 @@ void batchInsert() throws SQLException { void rowBatchInsert() throws SQLException { when(connectionMock.prepareStatement("insert into \"tab\" (\"c1\",\"c2\") values (?,?)")) .thenReturn(preparedStatementMock); - testee().rowBatchInsert().into("tab", List.of("c1", "c2")).mapping(row -> new Object[] { row }) + testee().rowPreparedStatementBatch().into("tab", List.of("c1", "c2")).mapping(row -> new Object[] { row }) .rows(Stream.of("one")).maxBatchSize(4).start(); final InOrder inOrder = inOrder(connectionMock, preparedStatementMock); inOrder.verify(connectionMock).prepareStatement("insert into \"tab\" (\"c1\",\"c2\") values (?,?)"); diff --git a/src/test/java/org/itsallcode/jdbc/SimpleConnectionITest.java b/src/test/java/org/itsallcode/jdbc/SimpleConnectionITest.java index 6cf7222..e88c292 100644 --- a/src/test/java/org/itsallcode/jdbc/SimpleConnectionITest.java +++ b/src/test/java/org/itsallcode/jdbc/SimpleConnectionITest.java @@ -9,6 +9,7 @@ import java.util.*; import java.util.stream.Stream; +import org.itsallcode.jdbc.batch.PreparedStatementBatch; import org.itsallcode.jdbc.batch.StatementBatch; import org.itsallcode.jdbc.dialect.H2Dialect; import org.itsallcode.jdbc.resultset.RowMapper; @@ -213,7 +214,7 @@ void executeQueryOnlyOneIteratorAllowed() { void batchInsertEmptyInput() { try (SimpleConnection connection = H2TestFixture.createMemConnection()) { connection.executeScript("CREATE TABLE TEST(ID INT, NAME VARCHAR(255))"); - connection.batchInsert(Object[].class).into("TEST", List.of("ID", "NAME")) + connection.preparedStatementBatch(Object[].class).into("TEST", List.of("ID", "NAME")) .mapping(ParamConverter.identity()) .rows(Stream.empty()).start(); @@ -226,7 +227,7 @@ void batchInsertEmptyInput() { void batchInsert() { try (SimpleConnection connection = H2TestFixture.createMemConnection()) { connection.executeScript("CREATE TABLE TEST(ID INT, NAME VARCHAR(255))"); - connection.batchInsert(Object[].class).into("TEST", List.of("ID", "NAME")) + connection.preparedStatementBatch(Object[].class).into("TEST", List.of("ID", "NAME")) .mapping(ParamConverter.identity()).rows( Stream.of(new Object[] { 1, "a" }, new Object[] { 2, "b" }, new Object[] { 3, "c" })) .start(); @@ -243,7 +244,7 @@ void batchInsert() { void insert() { try (SimpleConnection connection = H2TestFixture.createMemConnection()) { connection.executeScript("CREATE TABLE TEST(ID INT, NAME VARCHAR(255))"); - connection.batchInsert(Object[].class).into("TEST", List.of("ID", "NAME")) + connection.preparedStatementBatch(Object[].class).into("TEST", List.of("ID", "NAME")) .mapping(ParamConverter.identity()) .rows( Stream.of(new Object[] { 1, "a" }, new Object[] { 2, "b" }, new Object[] { 3, "c" })) @@ -257,10 +258,68 @@ void insert() { } } + @Test + void batchUpdate() { + try (SimpleConnection connection = H2TestFixture.createMemConnection()) { + connection.executeScript("CREATE TABLE TEST(ID INT, NAME VARCHAR(255))"); + connection.preparedStatementBatch(Object[].class).into("TEST", List.of("ID", "NAME")) + .mapping(ParamConverter.identity()) + .rows( + Stream.of(new Object[] { 1, "a" }, new Object[] { 2, "b" }, new Object[] { 3, "c" })) + .start(); + + connection.preparedStatementBatch(Object[].class).sql("UPDATE TEST SET NAME = ? WHERE ID = ?") + .mapping(ParamConverter.identity()) + .rows( + Stream.of(new Object[] { "a1", 1 }, new Object[] { "b2", 2 }, new Object[] { "c3", 3 })) + .start(); + + final List> result = connection.query("select * from test").stream() + .map(row -> row.columnValues().stream().map(ColumnValue::value).toList()).toList(); + assertAll( + () -> assertThat(result).hasSize(3), + () -> assertThat(result).isEqualTo(List.of(List.of(1, "a1"), List.of(2, "b2"), List.of(3, "c3")))); + } + } + + @Test + void directBatchUpdate() { + try (SimpleConnection connection = H2TestFixture.createMemConnection()) { + connection.executeScript("CREATE TABLE TEST(ID INT, NAME VARCHAR(255))"); + connection.preparedStatementBatch(Object[].class).into("TEST", List.of("ID", "NAME")) + .mapping(ParamConverter.identity()) + .rows( + Stream.of(new Object[] { 1, "a" }, new Object[] { 2, "b" }, new Object[] { 3, "c" })) + .start(); + + try (PreparedStatementBatch batch = connection.preparedStatementBatch() + .sql("UPDATE TEST SET NAME = ? WHERE ID = ?").build()) { + batch.add(ps -> { + ps.setString(1, "a1"); + ps.setInt(2, 1); + }); + batch.add(ps -> { + ps.setString(1, "b2"); + ps.setInt(2, 2); + }); + batch.add(ps -> { + ps.setString(1, "c3"); + ps.setInt(2, 3); + }); + } + + final List> result = connection.query("select * from test").stream() + .map(row -> row.columnValues().stream().map(ColumnValue::value).toList()).toList(); + assertAll( + () -> assertThat(result).hasSize(3), + () -> assertThat(result).isEqualTo(List.of(List.of(1, "a1"), List.of(2, "b2"), List.of(3, "c3")))); + } + } + @Test void batchStatement() { try (SimpleConnection connection = H2TestFixture.createMemConnection()) { - try (StatementBatch batch = connection.batch().maxBatchSize(3).build()) { + try (StatementBatch batch = connection.statementBatch().maxBatchSize(3).build()) { batch.addBatch("CREATE TABLE TEST(ID INT, NAME VARCHAR(255))"); batch.addBatch("INSERT INTO TEST VALUES (1, 'a')"); batch.addBatch("INSERT INTO TEST VALUES (2, 'b')"); diff --git a/src/test/java/org/itsallcode/jdbc/SimpleConnectionTest.java b/src/test/java/org/itsallcode/jdbc/SimpleConnectionTest.java index 31919a1..10435c5 100644 --- a/src/test/java/org/itsallcode/jdbc/SimpleConnectionTest.java +++ b/src/test/java/org/itsallcode/jdbc/SimpleConnectionTest.java @@ -72,9 +72,9 @@ static Stream operations() { operation(con -> con.query("sql", preparedStatementSetterMock, rowMapperMock)), operation(con -> con.query("sql", List.of(), rowMapperMock)), operation(con -> con.getOriginalConnection()), - operation(con -> con.batch()), - operation(con -> con.batchInsert()), - operation(con -> con.batchInsert(null))); + operation(con -> con.statementBatch()), + operation(con -> con.preparedStatementBatch()), + operation(con -> con.preparedStatementBatch(null))); } static Arguments operation(final Consumer operation) { diff --git a/src/test/java/org/itsallcode/jdbc/TransactionITest.java b/src/test/java/org/itsallcode/jdbc/TransactionITest.java index 8005c5e..e1d989f 100644 --- a/src/test/java/org/itsallcode/jdbc/TransactionITest.java +++ b/src/test/java/org/itsallcode/jdbc/TransactionITest.java @@ -116,7 +116,7 @@ void queryWithParamSetter() { void batchStatement() { try (SimpleConnection connection = H2TestFixture.createMemConnection()) { try (Transaction tx = connection.startTransaction()) { - try (StatementBatch batch = tx.batch().maxBatchSize(3).build()) { + try (StatementBatch batch = tx.statementBatch().maxBatchSize(3).build()) { batch.addBatch("CREATE TABLE TEST(ID INT, NAME VARCHAR(255))"); batch.addBatch("INSERT INTO TEST VALUES (1, 'a')"); batch.addBatch("INSERT INTO TEST VALUES (2, 'b')"); @@ -139,7 +139,7 @@ void batchInsert() { try (SimpleConnection connection = H2TestFixture.createMemConnection()) { connection.executeScript("CREATE TABLE TEST(ID INT, NAME VARCHAR(255))"); try (Transaction tx = connection.startTransaction()) { - tx.batchInsert(String.class).into("TEST", List.of("ID", "NAME")) + tx.preparedStatementBatch(String.class).into("TEST", List.of("ID", "NAME")) .mapping(row -> new Object[] { row.length(), row }) .rows(Stream.of("a", "ab", "abc")).start(); final List resultSet = tx.query("select count(*) as result from test") diff --git a/src/test/java/org/itsallcode/jdbc/TransactionTest.java b/src/test/java/org/itsallcode/jdbc/TransactionTest.java index 8ccf520..5f0224c 100644 --- a/src/test/java/org/itsallcode/jdbc/TransactionTest.java +++ b/src/test/java/org/itsallcode/jdbc/TransactionTest.java @@ -74,9 +74,9 @@ static Stream operations() { operation(tx -> tx.query("sql", rowMapperMock)), operation(tx -> tx.query("sql", preparedStatementSetterMock, rowMapperMock)), operation(tx -> tx.query("sql", List.of(), rowMapperMock)), - operation(tx -> tx.batch()), - operation(tx -> tx.batchInsert()), - operation(tx -> tx.batchInsert(null)), + operation(tx -> tx.statementBatch()), + operation(tx -> tx.preparedStatementBatch()), + operation(tx -> tx.preparedStatementBatch(null)), operation(tx -> tx.getOriginalConnection()), operation(tx -> tx.commit()), operation(tx -> tx.rollback())); diff --git a/src/test/java/org/itsallcode/jdbc/batch/BatchInsertTest.java b/src/test/java/org/itsallcode/jdbc/batch/PreparedStatementBatchTest.java similarity index 83% rename from src/test/java/org/itsallcode/jdbc/batch/BatchInsertTest.java rename to src/test/java/org/itsallcode/jdbc/batch/PreparedStatementBatchTest.java index 6cf26b3..d2d9354 100644 --- a/src/test/java/org/itsallcode/jdbc/batch/BatchInsertTest.java +++ b/src/test/java/org/itsallcode/jdbc/batch/PreparedStatementBatchTest.java @@ -15,7 +15,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class BatchInsertTest { +class PreparedStatementBatchTest { @Mock SimplePreparedStatement stmtMock; @@ -24,7 +24,7 @@ class BatchInsertTest { @Test void addDoesNotFlush() { - final BatchInsert testee = testee(2); + final PreparedStatementBatch testee = testee(2); testee.add(stmtSetterMock); final InOrder inOrder = inOrder(stmtMock); @@ -35,7 +35,7 @@ void addDoesNotFlush() { @Test void addFlushesAfterBatchSizeReached() { - final BatchInsert testee = testee(2); + final PreparedStatementBatch testee = testee(2); when(stmtMock.executeBatch()).thenReturn(new int[0]); testee.add(stmtSetterMock); @@ -52,7 +52,7 @@ void addFlushesAfterBatchSizeReached() { @Test void addBatchDoesNotFlush() { - final BatchInsert testee = testee(2); + final PreparedStatementBatch testee = testee(2); testee.addBatch(); final InOrder inOrder = inOrder(stmtMock); @@ -62,7 +62,7 @@ void addBatchDoesNotFlush() { @Test void addBatchFlushesAfterBatchSizeReached() { - final BatchInsert testee = testee(2); + final PreparedStatementBatch testee = testee(2); when(stmtMock.executeBatch()).thenReturn(new int[0]); testee.addBatch(); @@ -76,14 +76,14 @@ void addBatchFlushesAfterBatchSizeReached() { @Test void closeClosesStatement() { - final BatchInsert testee = testee(2); + final PreparedStatementBatch testee = testee(2); testee.close(); verify(stmtMock).close(); } @Test void closeFlushes() { - final BatchInsert testee = testee(2); + final PreparedStatementBatch testee = testee(2); when(stmtMock.executeBatch()).thenReturn(new int[0]); testee.add(stmtSetterMock); @@ -98,13 +98,13 @@ void closeFlushes() { @Test void getStatement() { - final BatchInsert testee = testee(2); + final PreparedStatementBatch testee = testee(2); final PreparedStatement preparedStmtMock = mock(PreparedStatement.class); when(stmtMock.getStatement()).thenReturn(preparedStmtMock); assertThat(testee.getStatement()).isSameAs(preparedStmtMock); } - BatchInsert testee(final int maxBatchSize) { - return new BatchInsert(stmtMock, maxBatchSize); + PreparedStatementBatch testee(final int maxBatchSize) { + return new PreparedStatementBatch(stmtMock, maxBatchSize); } } diff --git a/src/test/java/org/itsallcode/jdbc/batch/RowBatchInsertTest.java b/src/test/java/org/itsallcode/jdbc/batch/RowPreparedStatementBatchTest.java similarity index 80% rename from src/test/java/org/itsallcode/jdbc/batch/RowBatchInsertTest.java rename to src/test/java/org/itsallcode/jdbc/batch/RowPreparedStatementBatchTest.java index cb9099f..ebbc941 100644 --- a/src/test/java/org/itsallcode/jdbc/batch/RowBatchInsertTest.java +++ b/src/test/java/org/itsallcode/jdbc/batch/RowPreparedStatementBatchTest.java @@ -12,7 +12,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class RowBatchInsertTest { +class RowPreparedStatementBatchTest { @Mock SimplePreparedStatement stmtMock; @@ -21,7 +21,7 @@ class RowBatchInsertTest { @Test void addDoesNotFlush() { - final RowBatchInsert testee = testee(2); + final RowPreparedStatementBatch testee = testee(2); testee.add(new Row()); final InOrder inOrder = inOrder(stmtMock); @@ -32,7 +32,7 @@ void addDoesNotFlush() { @Test void addFlushesAfterBatchSizeReached() { - final RowBatchInsert testee = testee(2); + final RowPreparedStatementBatch testee = testee(2); when(stmtMock.executeBatch()).thenReturn(new int[0]); testee.add(new Row()); @@ -49,14 +49,14 @@ void addFlushesAfterBatchSizeReached() { @Test void closeClosesStatement() { - final RowBatchInsert testee = testee(2); + final RowPreparedStatementBatch testee = testee(2); testee.close(); verify(stmtMock).close(); } @Test void closeFlushes() { - final RowBatchInsert testee = testee(2); + final RowPreparedStatementBatch testee = testee(2); when(stmtMock.executeBatch()).thenReturn(new int[0]); testee.add(new Row()); @@ -69,8 +69,8 @@ void closeFlushes() { inOrder.verifyNoMoreInteractions(); } - RowBatchInsert testee(final int maxBatchSize) { - return new RowBatchInsert<>(stmtMock, stmtSetterMock, maxBatchSize); + RowPreparedStatementBatch testee(final int maxBatchSize) { + return new RowPreparedStatementBatch<>(stmtMock, stmtSetterMock, maxBatchSize); } record Row() { diff --git a/src/test/java/org/itsallcode/jdbc/example/ExampleTest.java b/src/test/java/org/itsallcode/jdbc/example/ExampleTest.java index 8f2ae34..a016827 100644 --- a/src/test/java/org/itsallcode/jdbc/example/ExampleTest.java +++ b/src/test/java/org/itsallcode/jdbc/example/ExampleTest.java @@ -13,7 +13,7 @@ import java.util.stream.Stream; import org.itsallcode.jdbc.*; -import org.itsallcode.jdbc.batch.BatchInsert; +import org.itsallcode.jdbc.batch.PreparedStatementBatch; import org.itsallcode.jdbc.batch.StatementBatch; import org.itsallcode.jdbc.resultset.SimpleResultSet; import org.itsallcode.jdbc.resultset.generic.Row; @@ -66,7 +66,7 @@ void exampleBatchStatement() { final ConnectionFactory connectionFactory = ConnectionFactory .create(Context.builder().build()); try (SimpleConnection connection = connectionFactory.create("jdbc:h2:mem:", "user", "password")) { - try (StatementBatch batch = connection.batch().maxBatchSize(3).build()) { + try (StatementBatch batch = connection.statementBatch().maxBatchSize(3).build()) { batch.addBatch("CREATE TABLE TEST(ID INT, NAME VARCHAR(255))"); batch.addBatch("INSERT INTO TEST VALUES (1, 'a')"); batch.addBatch("INSERT INTO TEST VALUES (2, 'b')"); @@ -88,7 +88,7 @@ void exampleRowBatchInsert() { .create(Context.builder().build()); try (SimpleConnection connection = connectionFactory.create("jdbc:h2:mem:", "user", "password")) { connection.executeScript(readResource("/schema.sql")); - connection.batchInsert(Name.class) + connection.preparedStatementBatch(Name.class) .into("NAMES", List.of("ID", "NAME")) .rows(Stream.of(new Name(1, "a"), new Name(2, "b"), new Name(3, "c"))) .mapping(Name::setPreparedStatement) @@ -110,7 +110,7 @@ void exampleDirectBatchInsert() { try (SimpleConnection connection = connectionFactory.create("jdbc:h2:mem:", "user", "password")) { try (Transaction transaction = connection.startTransaction()) { transaction.executeScript(readResource("/schema.sql")); - try (BatchInsert batch = transaction.batchInsert() + try (PreparedStatementBatch batch = transaction.preparedStatementBatch() .into("NAMES", List.of("ID", "NAME")) .maxBatchSize(100) .build()) { @@ -142,7 +142,7 @@ void exampleRawBatchInsert() throws SQLException { try (SimpleConnection connection = connectionFactory.create("jdbc:h2:mem:", "user", "password")) { try (Transaction transaction = connection.startTransaction()) { transaction.executeScript(readResource("/schema.sql")); - try (BatchInsert batch = transaction.batchInsert() + try (PreparedStatementBatch batch = transaction.preparedStatementBatch() .into("NAMES", List.of("ID", "NAME")) .maxBatchSize(100) .build()) {