Index: src/main/groovy/sql/Sql.java =================================================================== --- src/main/groovy/sql/Sql.java (revision 19656) +++ src/main/groovy/sql/Sql.java Wed Mar 24 15:35:09 EST 2010 @@ -201,6 +201,8 @@ private boolean enableNamedQueries = true; + private boolean withinBatch; + private final Map statementCache = new HashMap(); private final Map namedParamSqlCache = new HashMap(); private final Map> namedParamIndexPropCache = new HashMap>(); @@ -2105,7 +2107,36 @@ } /** + * Returns true if the current Sql object is currently executing a withBatch + * method call. + * + * @return true if a withBatch call is currently being executed. + */ + public boolean isWithinBatch() { + return withinBatch; + } + + /** * Performs the closure within a batch using a cached connection. + * Uses a batch size of zero, i.e. no automatic partitioning of batches. + * + * @param closure the closure containing batch and optionally other statements + * @return an array of update counts containing one element for each + * command in the batch. The elements of the array are ordered according + * to the order in which commands were added to the batch. + * @throws SQLException if a database access error occurs, + * or this method is called on a closed Statement, or the + * driver does not support batch statements. Throws {@link java.sql.BatchUpdateException} + * (a subclass of SQLException) if one of the commands sent to the + * database fails to execute properly or attempts to return a result set. + * @see #withBatch(Closure, int) + */ + public synchronized int[] withBatch(Closure closure) throws SQLException { + return withBatch(closure, 0); + } + + /** + * Performs the closure within a batch using a cached connection. * The closure will be called with a single argument; the statement * associated with this batch. Use it like this: *
@@ -2117,26 +2148,36 @@
      * 
* * @param closure the closure containing batch and optionally other statements + * @param batchSize partition the batch into batchSize pieces, i.e. after batchSize + * addBatch() invocations, call executeBatch() automatically; + * 0 means manual calls to executeBatch are required * @return an array of update counts containing one element for each * command in the batch. The elements of the array are ordered according - * to the order in which commands were added to the batch. + * to the order in which commands were added to the batch. If batch partitioning + * is enabled, the array returned will be the results for the last batch executed. * @throws SQLException if a database access error occurs, - * or this method is called on a closed Statement, or the + * or this method is called on a closed Statement, or the - * driver does not support batch statements. Throws {@link java.sql.BatchUpdateException} + * driver does not support batch statements. Throws {@link java.sql.BatchUpdateException} - * (a subclass of SQLException) if one of the commands sent to the + * (a subclass of SQLException) if one of the commands sent to the - * database fails to execute properly or attempts to return a result set. + * database fails to execute properly or attempts to return a result set. */ - public synchronized int[] withBatch(Closure closure) throws SQLException { + public synchronized int[] withBatch(Closure closure, int batchSize) throws SQLException { boolean savedCacheConnection = cacheConnection; cacheConnection = true; Connection connection = null; Statement statement = null; boolean savedAutoCommit = true; + boolean savedWithinBatch = withinBatch; try { + withinBatch = true; connection = createConnection(); savedAutoCommit = connection.getAutoCommit(); connection.setAutoCommit(false); + if (batchSize == 0) { - statement = createStatement(connection); + statement = createStatement(connection); + } else { + statement = new BatchingStatementWrapper(createStatement(connection), 0, LOG, connection); + } closure.call(statement); int[] result = statement.executeBatch(); connection.commit(); @@ -2156,6 +2197,7 @@ cacheConnection = false; closeResources(connection, statement); cacheConnection = savedCacheConnection; + withinBatch = savedWithinBatch; if (dataSource != null && !cacheConnection) { useConnection = null; } Index: src/main/groovy/sql/BatchingStatementWrapper.java =================================================================== --- src/main/groovy/sql/BatchingStatementWrapper.java Wed Mar 24 15:35:08 EST 2010 +++ src/main/groovy/sql/BatchingStatementWrapper.java Wed Mar 24 15:35:08 EST 2010 @@ -0,0 +1,203 @@ +package groovy.sql; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.Statement; +import java.util.logging.Logger; + +/** + * Class which delegates to a Statement but keeps track of + * a batch count size. If the batch count reaches the predefined number, + * this Statement does an executeBatch() automatically. + */ +public class BatchingStatementWrapper implements Statement { + private Statement delegate; + private int batchSize; + private int batchCount; + private Connection connection; + private Logger log; + + public BatchingStatementWrapper(Statement delegate, int batchSize, Logger log, Connection connection) { + this.delegate = delegate; + this.batchSize = batchSize; + this.connection = connection; + this.log = log; + this.batchCount = 0; + } + + public void addBatch(String sql) throws SQLException { + delegate.addBatch(sql); + batchCount++; + if (batchCount % batchSize == 0) { + int[] result = delegate.executeBatch(); + connection.commit(); + log.fine("Successfully executed batch with " + result.length + " command(s)"); + } + } + + public T unwrap(Class iface) throws SQLException { + return delegate.unwrap(iface); + } + + public boolean isWrapperFor(Class iface) throws SQLException { + return delegate.isWrapperFor(iface); + } + + public ResultSet executeQuery(String sql) throws SQLException { + return delegate.executeQuery(sql); + } + + public int executeUpdate(String sql) throws SQLException { + return delegate.executeUpdate(sql); + } + + public void close() throws SQLException { + delegate.close(); + } + + public int getMaxFieldSize() throws SQLException { + return delegate.getMaxFieldSize(); + } + + public void setMaxFieldSize(int max) throws SQLException { + delegate.setMaxFieldSize(max); + } + + public int getMaxRows() throws SQLException { + return delegate.getMaxRows(); + } + + public void setMaxRows(int max) throws SQLException { + delegate.setMaxRows(max); + } + + public void setEscapeProcessing(boolean enable) throws SQLException { + delegate.setEscapeProcessing(enable); + } + + public int getQueryTimeout() throws SQLException { + return delegate.getQueryTimeout(); + } + + public void setQueryTimeout(int seconds) throws SQLException { + delegate.setQueryTimeout(seconds); + } + + public void cancel() throws SQLException { + delegate.cancel(); + } + + public SQLWarning getWarnings() throws SQLException { + return delegate.getWarnings(); + } + + public void clearWarnings() throws SQLException { + delegate.clearWarnings(); + } + + public void setCursorName(String name) throws SQLException { + delegate.setCursorName(name); + } + + public boolean execute(String sql) throws SQLException { + return delegate.execute(sql); + } + + public ResultSet getResultSet() throws SQLException { + return delegate.getResultSet(); + } + + public int getUpdateCount() throws SQLException { + return delegate.getUpdateCount(); + } + + public boolean getMoreResults() throws SQLException { + return delegate.getMoreResults(); + } + + public void setFetchDirection(int direction) throws SQLException { + delegate.setFetchDirection(direction); + } + + public int getFetchDirection() throws SQLException { + return delegate.getFetchDirection(); + } + + public void setFetchSize(int rows) throws SQLException { + delegate.setFetchSize(rows); + } + + public int getFetchSize() throws SQLException { + return delegate.getFetchSize(); + } + + public int getResultSetConcurrency() throws SQLException { + return delegate.getResultSetConcurrency(); + } + + public int getResultSetType() throws SQLException { + return delegate.getResultSetType(); + } + + public void clearBatch() throws SQLException { + delegate.clearBatch(); + } + + public int[] executeBatch() throws SQLException { + return delegate.executeBatch(); + } + + public Connection getConnection() throws SQLException { + return delegate.getConnection(); + } + + public boolean getMoreResults(int current) throws SQLException { + return delegate.getMoreResults(current); + } + + public ResultSet getGeneratedKeys() throws SQLException { + return delegate.getGeneratedKeys(); + } + + public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException { + return delegate.executeUpdate(sql, autoGeneratedKeys); + } + + public int executeUpdate(String sql, int[] columnIndexes) throws SQLException { + return delegate.executeUpdate(sql, columnIndexes); + } + + public int executeUpdate(String sql, String[] columnNames) throws SQLException { + return delegate.executeUpdate(sql, columnNames); + } + + public boolean execute(String sql, int autoGeneratedKeys) throws SQLException { + return delegate.execute(sql, autoGeneratedKeys); + } + + public boolean execute(String sql, int[] columnIndexes) throws SQLException { + return delegate.execute(sql, columnIndexes); + } + + public boolean execute(String sql, String[] columnNames) throws SQLException { + return delegate.execute(sql, columnNames); + } + + public int getResultSetHoldability() throws SQLException { + return delegate.getResultSetHoldability(); + } + + public boolean isClosed() throws SQLException { + return delegate.isClosed(); + } + + public void setPoolable(boolean poolable) throws SQLException { + delegate.setPoolable(poolable); + } + + public boolean isPoolable() throws SQLException { + return delegate.isPoolable(); + } +}