1 package org.synchronoss.cpo.jdbc;
2
3 /*-
4 * [[
5 * jdbc
6 * ==
7 * Copyright (C) 2003 - 2026 Exaxis LLC, Synchronoss Technologies Inc
8 * ==
9 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU Lesser General Public License as
11 * published by the Free Software Foundation, either version 3 of the
12 * License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Lesser Public License for more details.
18 *
19 * You should have received a copy of the GNU General Lesser Public
20 * License along with this program. If not, see
21 * <http://www.gnu.org/licenses/lgpl-3.0.html>.
22 * ]]
23 */
24
25 import java.sql.PreparedStatement;
26 import java.sql.SQLException;
27 import java.util.List;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30 import org.synchronoss.cpo.core.CpoException;
31 import org.synchronoss.cpo.core.meta.domain.CpoFunction;
32
33 /**
34 * Executes a single-function update over a list of beans as JDBC batches. This class owns the
35 * batching mechanics — chunking by batch size, accumulating driver update counts, and surfacing
36 * per-statement failures — while {@link JdbcCpoAdapter} remains responsible for meta lookup,
37 * statement creation, logging, and transaction control.
38 *
39 * @author david berry
40 */
41 class JdbcBatchExecutor {
42
43 private static final Logger logger = LoggerFactory.getLogger(JdbcBatchExecutor.class);
44
45 private final int batchSize;
46
47 /**
48 * Constructs a JdbcBatchExecutor
49 *
50 * @param batchSize The number of statements to accumulate before executing a batch; zero or
51 * negative executes everything as one batch
52 */
53 JdbcBatchExecutor(int batchSize) {
54 this.batchSize = batchSize;
55 }
56
57 /**
58 * Adds every bean to the statement factory's prepared statement as a batch, executing each time
59 * the batch size is reached, and closes the statement when done. The factory must already be
60 * bound to the first bean.
61 *
62 * @param <T> The bean type
63 * @param jpsf The statement factory, bound to the first bean
64 * @param cpoFunction The single function being batched
65 * @param beans The beans to update
66 * @return The number of records updated
67 * @throws CpoException An error occurred binding a bean's values
68 * @throws SQLException An error occurred executing a batch
69 */
70 <T> long executeBatchedUpdates(
71 JdbcPreparedStatementFactory jpsf, CpoFunction cpoFunction, List<T> beans)
72 throws CpoException, SQLException {
73 PreparedStatement ps = jpsf.getPreparedStatement();
74 try {
75 long updateCount = 0;
76 ps.addBatch();
77 int batchCount = 1;
78 for (T bean : beans.subList(1, beans.size())) {
79 jpsf.setBindValues(jpsf.getBindValues(cpoFunction, bean));
80 ps.addBatch();
81 if (batchSize > 0 && ++batchCount % batchSize == 0) {
82 updateCount += executeBatch(ps);
83 }
84 }
85 updateCount += executeBatch(ps);
86 return updateCount;
87 } finally {
88 try {
89 ps.close();
90 } catch (Exception e) {
91 if (logger.isTraceEnabled()) {
92 logger.trace(e.getMessage());
93 }
94 }
95 }
96 }
97
98 private long executeBatch(PreparedStatement ps) throws SQLException {
99 long updateCount = 0;
100 int failedCount = 0;
101 int[] updates = ps.executeBatch();
102 for (int update : updates) {
103 if (update == PreparedStatement.SUCCESS_NO_INFO) {
104 // something updated but we do not know what or how many so default to one.
105 updateCount++;
106 } else if (update == PreparedStatement.EXECUTE_FAILED) {
107 // some drivers report per-statement failure here instead of throwing
108 // BatchUpdateException
109 failedCount++;
110 } else {
111 updateCount += update;
112 }
113 }
114 if (failedCount > 0) {
115 throw new SQLException(
116 failedCount + " of " + updates.length + " statements in the batch failed to execute");
117 }
118 return updateCount;
119 }
120 }