/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jdbi.v3.core.statement; import static org.jdbi.v3.core.result.ResultProducers.returningGeneratedKeys; import static org.jdbi.v3.core.result.ResultProducers.returningResults; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import org.jdbi.v3.core.Handle; import org.jdbi.v3.core.result.ResultBearing; import org.jdbi.v3.core.result.ResultIterator; import org.jdbi.v3.core.result.ResultProducer; import org.jdbi.v3.core.result.ResultSetMapper; import org.jdbi.v3.core.result.UnableToProduceResultException; import org.jdbi.v3.core.rewriter.RewrittenStatement; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Represents a prepared batch statement. Multiple bindings are added to the * compiled statement and then executed in a single operation. This is, generally, * a very efficient way to execute large numbers of the same statement where * the statement only varies by the arguments bound to it. * * The statement starts with an empty binding. You bind a single batch of parameters * with the usual {@link SqlStatement} binding methods, and then call * {@link PreparedBatch#add()} to add the current binding as a batch and then clear it. * * An entire batch can be bound and added in one go with {@link PreparedBatch#add(Map)} * or {@link PreparedBatch#add(Object...)}. */ public class PreparedBatch extends SqlStatement<PreparedBatch> implements ResultBearing { private static final Logger LOG = LoggerFactory.getLogger(PreparedBatch.class); private final List<Binding> bindings = new ArrayList<>(); public PreparedBatch(Handle handle, String sql) { super(handle, sql); } @Override public <R> R mapResultSet(ResultSetMapper<R> mapper) { return execute(returningResults()).mapResultSet(mapper); } /** * Execute the batch * * @return the number of rows modified or inserted per batch part. */ public int[] execute() { return internalBatchExecute().updateCounts; } public ResultIterator<Integer> executeAndGetModCount() { StatementContext ctx = getContext(); final int[] modCount = execute(); return new ResultIterator<Integer>() { int pos = 0; @Override public boolean hasNext() { return pos < modCount.length; } @Override public Integer next() { if (!hasNext()) { throw new NoSuchElementException(); } return modCount[pos++]; } @Override public StatementContext getContext() { return ctx; } @Override public void close() { } }; } public ResultBearing executeAndReturnGeneratedKeys(String... columnNames) { return execute(returningGeneratedKeys(columnNames)); } /** * Executes the batch, returning the result obtained from the given {@link ResultProducer}. * * @param producer the result producer. * @return value returned by the result producer. */ public <R> R execute(ResultProducer<R> producer) { try { return producer.produce(() -> internalBatchExecute().stmt, getContext()); } catch (SQLException e) { try { close(); } catch (Exception e1) { e.addSuppressed(e1); } throw new UnableToProduceResultException("Exception producing batch result", e, getContext()); } } private static class ExecutedBatch { final PreparedStatement stmt; final int[] updateCounts; ExecutedBatch(PreparedStatement stmt, int[] updateCounts) { this.stmt = stmt; this.updateCounts = updateCounts; } } private ExecutedBatch internalBatchExecute() { if (!getBinding().isEmpty()) { add(); } if (bindings.isEmpty()) { throw new IllegalStateException("No batch parts to execute"); } RewrittenStatement rewritten = getConfig(SqlStatements.class) .getStatementRewriter() .rewrite(getSql(), bindings.get(0), getContext()); try { final PreparedStatement stmt; String sql = rewritten.getSql(); try { StatementBuilder statementBuilder = getHandle().getStatementBuilder(); Connection connection = getHandle().getConnection(); stmt = statementBuilder.create(connection, sql, getContext()); addCleanable(() -> statementBuilder.close(connection, sql, stmt)); } catch (SQLException e) { throw new UnableToCreateStatementException(e, getContext()); } try { for (Binding binding : bindings) { rewritten.bind(binding, stmt); stmt.addBatch(); } } catch (SQLException e) { throw new UnableToExecuteStatementException("Exception while binding parameters", e, getContext()); } beforeExecution(stmt); try { final long start = System.nanoTime(); final int[] rs = stmt.executeBatch(); final long elapsedTime = System.nanoTime() - start; LOG.trace("Prepared batch of {} parts executed in {}ms", bindings.size(), elapsedTime / 1000000L, sql); getConfig(SqlStatements.class).getTimingCollector().collect(elapsedTime, getContext()); afterExecution(stmt); return new ExecutedBatch(stmt, rs); } catch (SQLException e) { throw new UnableToExecuteStatementException(Batch.mungeBatchException(e), getContext()); } } finally { bindings.clear(); } } /** * Add the current binding as a saved batch and clear the binding. */ public PreparedBatch add() { bindings.add(getBinding()); getContext().setBinding(new Binding()); return this; } /** * Bind arguments positionally, add the binding as a saved batch, and * then clear the current binding. * @param args the positional arguments to bind * @return this */ public PreparedBatch add(Object... args) { for(int i = 0; i < args.length; i++) { bind(i, args[i]); } add(); return this; } /** * Bind arguments from a Map, add the binding as a saved batch, * then clear the current binding. * * @param args map to bind arguments from for named parameters on the statement * @return this */ public PreparedBatch add(Map<String, ?> args) { bindMap(args); add(); return this; } /** * @return the number of bindings which are in this batch */ public int size() { return bindings.size(); } }