diff --git a/src/main/java/org/itsallcode/jdbc/BatchInsert.java b/src/main/java/org/itsallcode/jdbc/BatchInsert.java index 5843baf..256f535 100644 --- a/src/main/java/org/itsallcode/jdbc/BatchInsert.java +++ b/src/main/java/org/itsallcode/jdbc/BatchInsert.java @@ -34,24 +34,24 @@ void add(final T row) { } } - @Override - public void close() { - executeBatch(); - LOG.fine(() -> "Batch insert of %d rows with batch size %d completed in %s".formatted(rows, maxBatchSize, - Duration.between(start, Instant.now()))); - statement.close(); - } - private void executeBatch() { if (currentBatchSize == 0) { LOG.finest("No rows added to batch, skip"); return; } final Instant batchStart = Instant.now(); - statement.executeBatch(); + final int[] result = statement.executeBatch(); final Duration duration = Duration.between(batchStart, Instant.now()); - LOG.finest(() -> "Execute batch of " + currentBatchSize + " after " + rows + " took " + duration.toMillis() - + " ms"); + LOG.finest(() -> "Execute batch of %d after %d took %d ms, result length: %s".formatted(currentBatchSize, rows, + duration.toMillis(), result.length)); currentBatchSize = 0; } + + @Override + public void close() { + executeBatch(); + LOG.fine(() -> "Batch insert of %d rows with batch size %d completed in %s".formatted(rows, maxBatchSize, + Duration.between(start, Instant.now()))); + statement.close(); + } } diff --git a/src/main/java/org/itsallcode/jdbc/BatchInsertBuilder.java b/src/main/java/org/itsallcode/jdbc/BatchInsertBuilder.java index 06738bd..d8985e4 100644 --- a/src/main/java/org/itsallcode/jdbc/BatchInsertBuilder.java +++ b/src/main/java/org/itsallcode/jdbc/BatchInsertBuilder.java @@ -11,6 +11,8 @@ /** * Builder for batch inserts. + * + * @param row type */ public class BatchInsertBuilder { private static final Logger LOG = Logger.getLogger(BatchInsertBuilder.class.getName()); @@ -27,6 +29,14 @@ public class BatchInsertBuilder { this.context = context; } + /** + * Define table and column names used for generating the {@code INSERT} + * statement. + * + * @param tableName table name + * @param columnNames column names + * @return {@code this} for fluent programming + */ @SuppressWarnings("java:S3242") // Using List instead of Collection to preserve column order public BatchInsertBuilder into(final String tableName, final List columnNames) { this.sql = createInsertStatement(Identifier.simple(tableName), @@ -34,16 +44,34 @@ public BatchInsertBuilder into(final String tableName, final List col return this; } + /** + * Define {@link Stream} of rows to insert. + * + * @param rows rows to insert + * @return {@code this} for fluent programming + */ public BatchInsertBuilder rows(final Stream rows) { final Iterator iterator = rows.iterator(); return rows(iterator); } + /** + * Define {@link Iterator} of rows to insert. + * + * @param rows rows to insert + * @return {@code this} for fluent programming + */ public BatchInsertBuilder rows(final Iterator rows) { this.rows = rows; return this; } + /** + * Define mapping how rows are converted to {@code Object[]} for inserting. + * + * @param rowMapper row mapper + * @return {@code this} for fluent programming + */ public BatchInsertBuilder mapping(final ParamConverter rowMapper) { final RowPreparedStatementSetter setter = new ObjectArrayPreparedStatementSetter( context.getParameterMapper()); @@ -52,11 +80,24 @@ public BatchInsertBuilder mapping(final ParamConverter rowMapper) { preparedStatement)); } + /** + * Define {@link RowPreparedStatementSetter} that sets values of a + * {@link PreparedStatement} for each row. + * + * @param preparedStatementSetter prepared statement setter + * @return {@code this} for fluent programming + */ public BatchInsertBuilder mapping(final RowPreparedStatementSetter preparedStatementSetter) { this.mapper = preparedStatementSetter; return this; } + /** + * Define maximum batch size, using {@link #DEFAULT_MAX_BATCH_SIZE} as default. + * + * @param maxBatchSize maximum batch size + * @return {@code this} for fluent programming + */ public BatchInsertBuilder maxBatchSize(final int maxBatchSize) { this.maxBatchSize = maxBatchSize; return this; @@ -68,6 +109,9 @@ private static String createInsertStatement(final Identifier table, final List row type */ diff --git a/src/main/java/org/itsallcode/jdbc/SimplePreparedStatement.java b/src/main/java/org/itsallcode/jdbc/SimplePreparedStatement.java index 6d64eab..ac0803a 100644 --- a/src/main/java/org/itsallcode/jdbc/SimplePreparedStatement.java +++ b/src/main/java/org/itsallcode/jdbc/SimplePreparedStatement.java @@ -51,9 +51,9 @@ void setValues(final PreparedStatementSetter preparedStatementSetter) { } - void executeBatch() { + int[] executeBatch() { try { - statement.executeBatch(); + return statement.executeBatch(); } catch (final SQLException e) { throw new UncheckedSQLException("Error executing batch sql '" + sql + "'", e); } diff --git a/src/test/java/org/itsallcode/jdbc/ExampleTest.java b/src/test/java/org/itsallcode/jdbc/ExampleTest.java index 514cca8..93b83e6 100644 --- a/src/test/java/org/itsallcode/jdbc/ExampleTest.java +++ b/src/test/java/org/itsallcode/jdbc/ExampleTest.java @@ -5,6 +5,8 @@ import java.io.*; import java.net.URL; import java.nio.charset.StandardCharsets; +import java.sql.PreparedStatement; +import java.sql.SQLException; import java.util.List; import java.util.stream.Stream; @@ -13,19 +15,24 @@ import org.junit.jupiter.api.Test; class ExampleTest { + record Name(int id, String name) { + static void setPreparedStatement(final Name row, final PreparedStatement stmt) throws SQLException { + stmt.setInt(1, row.id); + stmt.setString(2, row.name); + } + } + @Test void example() { - record Name(int id, String name) { - Object[] toRow() { - return new Object[] { id, name }; - } - } final ConnectionFactory connectionFactory = ConnectionFactory .create(Context.builder().build()); try (SimpleConnection connection = connectionFactory.create("jdbc:h2:mem:", "user", "password")) { connection.executeScript(readResource("/schema.sql")); - connection.batchInsert(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::toRow).start(); + connection.batchInsert(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) + .start(); try (SimpleResultSet rs = connection.query("select * from names order by id")) { final List result = rs.stream().toList();