View Javadoc
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 }