001package org.itsallcode.jdbc.batch; 002 003import java.sql.PreparedStatement; 004import java.util.*; 005import java.util.function.Function; 006import java.util.stream.Stream; 007 008import org.itsallcode.jdbc.*; 009import org.itsallcode.jdbc.identifier.Identifier; 010 011/** 012 * Builder for {@link PreparedStatement} batch jobs for a {@link Stream} or 013 * {@link Iterable} of row objects using e.g. {@code INSERT} or {@code UPDATE} 014 * statements. 015 * <p> 016 * Create a new instance using 017 * {@link DbOperations#preparedStatementBatch(Class)}. 018 * 019 * @param <T> row type 020 */ 021public class RowPreparedStatementBatchBuilder<T> { 022 private final PreparedStatementBatchBuilder baseBuilder; 023 private RowPreparedStatementSetter<T> mapper; 024 private Iterator<T> rows; 025 026 /** 027 * Create a new instance. 028 * 029 * @param statementFactory factory for creating {@link SimplePreparedStatement} 030 */ 031 public RowPreparedStatementBatchBuilder(final Function<String, SimplePreparedStatement> statementFactory) { 032 this(new PreparedStatementBatchBuilder(statementFactory)); 033 } 034 035 RowPreparedStatementBatchBuilder(final PreparedStatementBatchBuilder baseBuilder) { 036 this.baseBuilder = baseBuilder; 037 } 038 039 /** 040 * Define the SQL statement to be used for the batch job, e.g. {@code INSERT} or 041 * {@code UPDATE}. 042 * 043 * @param sql SQL statement 044 * @return {@code this} for fluent programming 045 */ 046 public RowPreparedStatementBatchBuilder<T> sql(final String sql) { 047 this.baseBuilder.sql(sql); 048 return this; 049 } 050 051 /** 052 * Define table and column names used for generating the {@code INSERT} 053 * statement. 054 * 055 * @param tableName table name 056 * @param columnNames column names 057 * @return {@code this} for fluent programming 058 */ 059 @SuppressWarnings("java:S3242") // Using List instead of Collection to preserve column order 060 public RowPreparedStatementBatchBuilder<T> into(final Identifier tableName, final List<Identifier> columnNames) { 061 this.baseBuilder.into(tableName, columnNames); 062 return this; 063 } 064 065 /** 066 * Define table and column names used for generating the {@code INSERT} 067 * statement. 068 * 069 * @param tableName table name 070 * @param columnNames column names 071 * @return {@code this} for fluent programming 072 */ 073 @SuppressWarnings("java:S3242") // Using List instead of Collection to preserve column order 074 public RowPreparedStatementBatchBuilder<T> into(final String tableName, final List<String> columnNames) { 075 return into(Identifier.simple(tableName), columnNames.stream().map(Identifier::simple).toList()); 076 } 077 078 /** 079 * Define {@link Stream} of rows to insert. 080 * 081 * @param rows rows to insert 082 * @return {@code this} for fluent programming 083 */ 084 public RowPreparedStatementBatchBuilder<T> rows(final Stream<T> rows) { 085 return rows(rows.iterator()); 086 } 087 088 /** 089 * Define {@link Iterator} of rows to insert. 090 * 091 * @param rows rows to insert 092 * @return {@code this} for fluent programming 093 */ 094 public RowPreparedStatementBatchBuilder<T> rows(final Iterator<T> rows) { 095 this.rows = rows; 096 return this; 097 } 098 099 /** 100 * Define mapping how rows are converted to {@code Object[]} for inserting. 101 * 102 * @param rowMapper row mapper 103 * @return {@code this} for fluent programming 104 */ 105 public RowPreparedStatementBatchBuilder<T> mapping(final ParamConverter<T> rowMapper) { 106 final RowPreparedStatementSetter<Object[]> setter = new ObjectArrayPreparedStatementSetter(); 107 return mapping( 108 (final T row, final PreparedStatement preparedStatement) -> setter.setValues(rowMapper.map(row), 109 preparedStatement)); 110 } 111 112 /** 113 * Define {@link RowPreparedStatementSetter} that sets values of a 114 * {@link PreparedStatement} for each row. 115 * 116 * @param preparedStatementSetter prepared statement setter 117 * @return {@code this} for fluent programming 118 */ 119 public RowPreparedStatementBatchBuilder<T> mapping(final RowPreparedStatementSetter<T> preparedStatementSetter) { 120 this.mapper = preparedStatementSetter; 121 return this; 122 } 123 124 /** 125 * Define maximum batch size, using 126 * {@link PreparedStatementBatchBuilder#DEFAULT_MAX_BATCH_SIZE} as default. 127 * 128 * @param maxBatchSize maximum batch size 129 * @return {@code this} for fluent programming 130 */ 131 public RowPreparedStatementBatchBuilder<T> maxBatchSize(final int maxBatchSize) { 132 this.baseBuilder.maxBatchSize(maxBatchSize); 133 return this; 134 } 135 136 /** 137 * Start the batch insert process using the given rows. 138 */ 139 public void start() { 140 Objects.requireNonNull(this.mapper, "mapper"); 141 Objects.requireNonNull(this.rows, "rows"); 142 try (PreparedStatementBatch batchInsert = baseBuilder.build(); 143 RowPreparedStatementBatch<T> rowBatchInsert = new RowPreparedStatementBatch<>(batchInsert, 144 this.mapper)) { 145 while (rows.hasNext()) { 146 rowBatchInsert.add(rows.next()); 147 } 148 } 149 } 150}