/*
* Copyright 2006-2012 The Scriptella Project Team.
*
* 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 scriptella.jdbc;
import scriptella.spi.ParametersCallback;
import scriptella.spi.QueryCallback;
import scriptella.util.ExceptionUtils;
import scriptella.util.IOUtils;
import java.io.Closeable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Abstraction for {@link java.sql.Statement} and {@link java.sql.PreparedStatement}.
*
* @author Fyodor Kupolov
* @version 1.0
*/
abstract class StatementWrapper<T extends Statement> implements Closeable {
private static final Logger LOG = Logger.getLogger(StatementWrapper.class.getName());
protected final JdbcTypesConverter converter;
protected final T statement;
/**
* For testing only.
*/
protected StatementWrapper() {
converter = null;
statement = null;
}
protected StatementWrapper(T statement, JdbcTypesConverter converter) {
if (statement == null) {
throw new IllegalArgumentException("statement cannot be null");
}
if (converter == null) {
throw new IllegalArgumentException("converter cannot be null");
}
this.statement = statement;
this.converter = converter;
}
/**
* Release any resources opened by this statement.
*/
public void close() {
JdbcUtils.closeSilent(statement);
}
/**
* Executes the given SQL statement, which may be an INSERT, UPDATE, or DELETE statement
* or an SQL statement that returns nothing, such as an SQL DDL statement.
*
* @return either the row count for INSERT, UPDATE, or DELETE statements or 0 for SQL statements that return nothing.
* @throws SQLException if JDBC driver fails to execute the operation.
*/
public abstract int update() throws SQLException;
/**
* Executes the query and returns the result set.
*
* @return result set with query result.
* @throws SQLException if JDBC driver fails to execute the operation.
*/
protected abstract ResultSet query() throws SQLException;
public void query(final QueryCallback queryCallback, final ParametersCallback parametersCallback) throws SQLException {
ResultSetAdapter r = null;
try {
r = new ResultSetAdapter(query(), parametersCallback, converter);
while (r.next()) {
queryCallback.processRow(r);
}
} finally {
IOUtils.closeSilently(r);
}
}
public void setParameters(final List<Object> params) throws SQLException {
}
/**
* Clears any transient state variables, e.g. statement parameters etc.
*/
public void clear() {
}
/**
* Flushes any pending operations.
*
* @return number of rows updated
* @throws SQLException if DB error occurs.
*/
public int flush() throws SQLException {
return 0;
}
/**
* @see java.sql.Statement#toString()
*/
public String toString() {
return statement.toString();
}
/**
* Helper method for executing a batch.
*
* @param statement statement to execute.
* @return sum of update counts for all commands.
* @throws SQLException if error occurs
*/
protected static int executeBatch(Statement statement) throws SQLException {
int result = 0;
int[] results = statement.executeBatch();
for (int r : results) {
if (r > 0) {
result += r;
}
}
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Batch of " + results.length + " statements executed.");
}
return result;
}
/**
* {@link Statement} wrapper.
*/
static class Simple extends StatementWrapper<Statement> {
protected final String sql;
/**
* For testing only.
*/
protected Simple(String sql) {
this.sql = sql;
}
public Simple(Statement s, String sql, JdbcTypesConverter converter) {
super(s, converter);
this.sql = sql;
}
@Override
public int update() throws SQLException {
return statement.executeUpdate(sql);
}
@Override
protected ResultSet query() throws SQLException {
return statement.executeQuery(sql);
}
}
/**
* {@link PreparedStatement} wrapper.
*/
static class Prepared extends StatementWrapper<PreparedStatement> {
/**
* For testing only.
*/
protected Prepared() {
}
public Prepared(PreparedStatement s, JdbcTypesConverter converter) {
super(s, converter);
}
/**
* Sets parameters for this statement.
* <p>Default implementation is noop.
*
* @param params parameters to set.
* @throws SQLException
*/
@Override
public void setParameters(List<Object> params) throws SQLException {
for (int i = 0, n = params.size(); i < n; i++) {
Object o = params.get(i);
converter.setObject(statement, i + 1, o);
}
}
@Override
public int update() throws SQLException {
try {
return statement.executeUpdate();
} finally {
converter.close(); //Disposing converter
}
}
@Override
protected ResultSet query() throws SQLException {
return statement.executeQuery();
}
@Override
public void clear() {
try {
statement.clearParameters();
} catch (SQLException e) {
ExceptionUtils.ignoreThrowable(e);
}
}
}
/**
* {@link StatementWrapper} for batching.
* <p>This instance is intended to be shared per ETL element.
* To overcome this and additional method {@link #setSql(String)} must be called prior to calling update.
*/
static class Batched extends StatementWrapper<Statement> {
private int maxBatchSize;
private int currentBatchSize;
private String sql;
/**
* For testing only.
*/
protected Batched() {
}
public Batched(Statement s, JdbcTypesConverter converter, int maxBatchSize) {
super(s, converter);
this.maxBatchSize = maxBatchSize;
}
public void setSql(String sql) {
this.sql = sql;
}
@Override
public int update() throws SQLException {
statement.addBatch(sql);
currentBatchSize++;
int result = 0;
if (currentBatchSize >= maxBatchSize) {
result = executeBatch();
}
return result;
}
/**
* Executes current batch.
*
* @return number of rows affected.
* @throws SQLException if error occurs
*/
protected int executeBatch() throws SQLException {
try {
return executeBatch(statement);
} finally {
currentBatchSize = 0;
converter.close(); //Disposing converter
}
}
@Override
protected ResultSet query() throws SQLException {
//In a very unlikely case when the same SQL was used for batch updates
flush();
return statement.executeQuery(sql);
}
@Override
public void clear() {
this.sql = null;
}
@Override
public int flush() throws SQLException {
if (currentBatchSize > 0) {
return executeBatch();
}
return 0;
}
@Override
public void close() {
super.close();
}
}
/**
* {@link Prepared} for batching.
*/
static class BatchedPrepared extends Prepared {
private int maxBatchSize;
private int currentBatchSize;
/**
* For testing only.
*/
protected BatchedPrepared() {
}
public BatchedPrepared(PreparedStatement s, JdbcTypesConverter converter, int maxBatchSize) {
super(s, converter);
this.maxBatchSize = maxBatchSize;
}
@Override
public int update() throws SQLException {
statement.addBatch();
currentBatchSize++;
int result = 0;
if (currentBatchSize >= maxBatchSize) {
result = executeBatch();
}
return result;
}
/**
* Executes current batch.
*
* @return number of rows affected.
* @throws SQLException if error occurs
*/
protected int executeBatch() throws SQLException {
try {
return executeBatch(statement);
} finally {
currentBatchSize = 0;
converter.close(); //Disposing converter
}
}
@Override
public void clear() {
//Do not clear parameters, until the batch is sent
}
@Override
public int flush() throws SQLException {
if (currentBatchSize > 0) {
return executeBatch();
}
return 0;
}
@Override
protected ResultSet query() throws SQLException {
//In a very unlikely case when the same SQL was used for batch updates
flush();
return super.query();
}
@Override
public void close() {
super.close();
}
}
}