diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/AbstractCursorItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/AbstractCursorItemReader.java new file mode 100644 index 000000000..f07cd0ade --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/AbstractCursorItemReader.java @@ -0,0 +1,485 @@ +/* + * Copyright 2006-2009 the original author or authors. + * + * 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.springframework.batch.item.database; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.Statement; + +import javax.sql.DataSource; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.batch.item.ExecutionContext; +import org.springframework.batch.item.ItemStream; +import org.springframework.batch.item.ReaderNotOpenException; +import org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.dao.InvalidDataAccessResourceUsageException; +import org.springframework.jdbc.SQLWarningException; +import org.springframework.jdbc.datasource.DataSourceUtils; +import org.springframework.jdbc.support.JdbcUtils; +import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator; +import org.springframework.jdbc.support.SQLExceptionTranslator; +import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.util.Assert; + +/** + *
+ * Abstract base class for any simple item reader that opens a database cursor and continually retrieves + * the next row in the ResultSet. + *
+ * + *+ * By default the cursor will be opened using a separate connection. The ResultSet for the cursor + * is held open regardless of commits or roll backs in a surrounding transaction. Clients of this + * reader are responsible for buffering the items in the case that they need to be re-presented on a + * rollback. This buffering is handled by the step implementations provided and is only a concern for + * anyone writing their own step implementations. + *
+ * + *
+ * There is an option ({@link #setUseSharedExtendedConnection(boolean)} that will share the connection
+ * used for the cursor with the rest of the step processing. If you set this flag to true
+ * then you must wrap the DataSource in a {@link ExtendedConnectionDataSourceProxy} to prevent the
+ * connection from being closed and released after each commit performed as part of the step processing.
+ * You must also use a JDBC driver supporting JDBC 3.0 or later since the cursor will be opened with the
+ * additional option of 'HOLD_CUSORS_OVER_COMMIT' enabled.
+ *
+ * Each call to {@link #read()} will attempt to map the row at the current position in the + * ResultSet. There is currently no wrapping of the ResultSet to suppress calls + * to next(). However, if the RowMapper (mistakenly) increments the current row, + * the next call to read will verify that the current row is at the expected + * position and throw a DataAccessException if it is not. The reason for such strictness on the + * ResultSet is due to the need to maintain control for transactions and + * restartability. This ensures that each call to {@link #read()} returns the + * ResultSet at the correct row, regardless of rollbacks or restarts. + *
+ * + *+ * {@link ExecutionContext}: The current row is returned as restart data, and + * when restored from that same data, the cursor is opened and the current row + * set to the value within the restart data. See + * {@link #setDriverSupportsAbsolute(boolean)} for improving restart + * performance. + *
+ * + *+ * Calling close on this {@link ItemStream} will cause all resources it is + * currently using to be freed. (Connection, ResultSet, etc). It is then illegal + * to call {@link #read()} again until it has been re-opened. + *
+ * + *
+ * Known limitation: when used with Derby
+ * {@link #setVerifyCursorPosition(boolean)} needs to be false
+ * because {@link ResultSet#getRow()} call used for cursor position verification
+ * is not available for 'TYPE_FORWARD_ONLY' result sets.
+ *
ResultSet object. If the fetch size specified is zero, the
+ * JDBC driver ignores the value.
+ *
+ * @param fetchSize the number of rows to fetch
+ * @see ResultSet#setFetchSize(int)
+ */
+ public void setFetchSize(int fetchSize) {
+ this.fetchSize = fetchSize;
+ }
+
+ /**
+ * Sets the limit for the maximum number of rows that any
+ * ResultSet object can contain to the given number.
+ *
+ * @param maxRows the new max rows limit; zero means there is no limit
+ * @see Statement#setMaxRows(int)
+ */
+ public void setMaxRows(int maxRows) {
+ this.maxRows = maxRows;
+ }
+
+ /**
+ * Sets the number of seconds the driver will wait for a
+ * Statement object to execute to the given number of seconds.
+ * If the limit is exceeded, an SQLException is thrown.
+ *
+ * @param queryTimeout seconds the new query timeout limit in seconds; zero
+ * means there is no limit
+ * @see Statement#setQueryTimeout(int)
+ */
+ public void setQueryTimeout(int queryTimeout) {
+ this.queryTimeout = queryTimeout;
+ }
+
+ /**
+ * Set whether SQLWarnings should be ignored (only logged) or exception
+ * should be thrown.
+ *
+ * @param ignoreWarnings if TRUE, warnings are ignored
+ */
+ public void setIgnoreWarnings(boolean ignoreWarnings) {
+ this.ignoreWarnings = ignoreWarnings;
+ }
+
+ /**
+ * Allow verification of cursor position after current row is processed by
+ * RowMapper or RowCallbackHandler. Default value is TRUE.
+ *
+ * @param verifyCursorPosition if true, cursor position is verified
+ */
+ public void setVerifyCursorPosition(boolean verifyCursorPosition) {
+ this.verifyCursorPosition = verifyCursorPosition;
+ }
+
+ /**
+ * Indicate whether the JDBC driver supports setting the absolute row on a
+ * {@link ResultSet}. It is recommended that this is set to
+ * true for JDBC drivers that supports ResultSet.absolute() as
+ * it may improve performance, especially if a step fails while working with
+ * a large data set.
+ *
+ * @see ResultSet#absolute(int)
+ *
+ * @param driverSupportsAbsolute false by default
+ */
+ public void setDriverSupportsAbsolute(boolean driverSupportsAbsolute) {
+ this.driverSupportsAbsolute = driverSupportsAbsolute;
+ }
+
+ /**
+ * Indicate whether the connection used for the cursor should be used by all other processing
+ * thus sharing the same transaction. If this is set to false, which is the default, then the
+ * cursor will be opened using in its connection and will not participate in any transactions
+ * started for the rest of the step processing. If you set this flag to true then you must
+ * wrap the DataSource in a {@link ExtendedConnectionDataSourceProxy} to prevent the
+ * connection from being closed and released after each commit.
+ *
+ * When you set this option to true then the statement used to open the cursor
+ * will be created with both 'READ_ONLY' and 'HOLD_CUSORS_OVER_COMMIT' options. This allows
+ * holding the cursor open over transaction start and commits performed in the step processing.
+ * To use this feature you need a database that supports this and a JDBC driver supporting
+ * JDBC 3.0 or later.
+ *
+ * @param useSharedExtendedConnection false by default
+ */
+ public void setUseSharedExtendedConnection(boolean useSharedExtendedConnection) {
+ this.useSharedExtendedConnection = useSharedExtendedConnection;
+ }
+
+ public boolean isUseSharedExtendedConnection() {
+ return useSharedExtendedConnection;
+ }
+
+ public abstract String getSql();
+
+ /**
+ * Check the result set is in synch with the currentRow attribute. This is
+ * important to ensure that the user hasn't modified the current row.
+ */
+ private void verifyCursorPosition(long expectedCurrentRow) throws SQLException {
+ if (verifyCursorPosition) {
+ if (expectedCurrentRow != this.rs.getRow()) {
+ throw new InvalidDataAccessResourceUsageException("Unexpected cursor position change.");
+ }
+ }
+ }
+
+ /**
+ * Close the cursor and database connection. Make call to cleanupOnClose so sub classes can cleanup
+ * any resources they have allocated.
+ */
+ protected void doClose() throws Exception {
+ initialized = false;
+ JdbcUtils.closeResultSet(this.rs);
+ rs = null;
+ cleanupOnClose();
+ if (useSharedExtendedConnection && dataSource instanceof ExtendedConnectionDataSourceProxy) {
+ ((ExtendedConnectionDataSourceProxy)dataSource).stopCloseSuppression(this.con);
+ if (!TransactionSynchronizationManager.isActualTransactionActive()) {
+ DataSourceUtils.releaseConnection(con, dataSource);
+ }
+ }
+ else {
+ JdbcUtils.closeConnection(this.con);
+ }
+ }
+
+ protected abstract void cleanupOnClose() throws Exception;
+
+ /**
+ * Execute the statement to open the cursor.
+ */
+ protected void doOpen() throws Exception {
+ Assert.state(!initialized, "Stream is already initialized. Close before re-opening.");
+ Assert.isNull(rs, "ResultSet still open! Close before re-opening.");
+ initializeConnection();
+ openCursor(con);
+ initialized = true;
+
+ }
+
+ protected void initializeConnection() {
+ Assert.state(getDataSource() != null, "DataSource must not be null.");
+
+ try {
+ if (useSharedExtendedConnection) {
+ if (!(getDataSource() instanceof ExtendedConnectionDataSourceProxy)) {
+ throw new InvalidDataAccessApiUsageException(
+ "You must use a ExtendedConnectionDataSourceProxy for the dataSource when " +
+ "useSharedExtendedConnection is set to true.");
+ }
+ this.con = DataSourceUtils.getConnection(dataSource);
+ ((ExtendedConnectionDataSourceProxy)dataSource).startCloseSuppression(this.con);
+ }
+ else {
+ this.con = dataSource.getConnection();
+ }
+ }
+ catch (SQLException se) {
+ close();
+ throw getExceptionTranslator().translate("Executing query", getSql(), se);
+ }
+ }
+
+ protected abstract void openCursor(Connection con);
+
+ /**
+ * Read next row and map it to item, verify cursor position if
+ * {@link #setVerifyCursorPosition(boolean)} is true.
+ */
+ protected T doRead() throws Exception {
+ if (rs == null) {
+ throw new ReaderNotOpenException("Reader must be open before it can be read.");
+ }
+
+ try {
+ if (!rs.next()) {
+ return null;
+ }
+ int currentRow = getCurrentItemCount();
+ T item = readCursor(rs, currentRow);
+ verifyCursorPosition(currentRow);
+ return item;
+ }
+ catch (SQLException se) {
+ throw getExceptionTranslator().translate("Attempt to process next row failed", getSql(), se);
+ }
+ }
+
+ /**
+ * Read the cursor and map to the type of object this reader should return. This method must be
+ * overriden by subclasses.
+ *
+ * @param rs The current result set
+ * @param currentRow Current position of the result set
+ * @return the mapped object at the cursor position
+ * @throws SQLException
+ */
+ protected abstract T readCursor(ResultSet rs, int currentRow) throws SQLException;
+
+ /**
+ * Use {@link ResultSet#absolute(int)} if possible, otherwise scroll by
+ * calling {@link ResultSet#next()}.
+ */
+ protected void jumpToItem(int itemIndex) throws Exception {
+ if (driverSupportsAbsolute) {
+ try {
+ rs.absolute(itemIndex);
+ }
+ catch (SQLException e) {
+ // Driver does not support rs.absolute(int) revert to
+ // traversing ResultSet
+ log.warn("The JDBC driver does not appear to support ResultSet.absolute(). Consider"
+ + " reverting to the default behavior setting the driverSupportsAbsolute to false", e);
+
+ moveCursorToRow(itemIndex);
+ }
+ }
+ else {
+ moveCursorToRow(itemIndex);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/JdbcCursorItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/JdbcCursorItemReader.java
index 2ad554418..3236f2aee 100644
--- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/JdbcCursorItemReader.java
+++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/JdbcCursorItemReader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2007 the original author or authors.
+ * Copyright 2006-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,47 +20,21 @@ import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
-import java.sql.SQLWarning;
-import java.sql.Statement;
-import javax.sql.DataSource;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.springframework.batch.item.ExecutionContext;
-import org.springframework.batch.item.ItemStream;
-import org.springframework.batch.item.ReaderNotOpenException;
-import org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader;
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.dao.InvalidDataAccessApiUsageException;
-import org.springframework.dao.InvalidDataAccessResourceUsageException;
-import org.springframework.jdbc.SQLWarningException;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.core.RowMapper;
-import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.jdbc.support.JdbcUtils;
-import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator;
-import org.springframework.jdbc.support.SQLExceptionTranslator;
-import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator;
-import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* - * Simple item reader that opens a JDBC cursor and continually retrieves the + * Simple item reader implementation that opens a JDBC cursor and continually retrieves the * next row in the ResultSet. *
* *- * By default the cursor will be opened using a separate connection. The ResultSet for the cursor - * is held open regardless of commits or roll backs in a surrounding transaction. Clients of this - * reader are responsible for buffering the items in the case that they need to be re-presented on a - * rollback. This buffering is handled by the step implementations provided and is only a concern for - * anyone writing their own step implementations. - *
- * - ** The statement used to open the cursor is created with the 'READ_ONLY' option since a non read-only * cursor may unnecessarily lock tables or rows. It is also opened with 'TYPE_FORWARD_ONLY' option. * By default the cursor will be opened using a separate connection which means that it will not participate @@ -68,44 +42,8 @@ import org.springframework.util.ClassUtils; *
* *
- * There is an option ({@link #setUseSharedExtendedConnection(boolean)} that will share the connection
- * used for the cursor with the rest of the step processing. If you set this flag to true
- * then you must wrap the DataSource in a {@link ExtendedConnectionDataSourceProxy} to prevent the
- * connection from being closed and released after each commit performed as part of the step processing.
- * You must also use a JDBC driver supporting JDBC 3.0 or later since the cursor will be opened with the
- * additional option of 'HOLD_CUSORS_OVER_COMMIT' enabled.
- *
* Each call to {@link #read()} will call the provided RowMapper, passing in the - * ResultSet. There is currently no wrapping of the ResultSet to suppress calls - * to next(). However, if the RowMapper (mistakenly) increments the current row, - * the next call to read will verify that the current row is at the expected - * position and throw a DataAccessException if it is not. The reason for such strictness on the - * ResultSet is due to the need to maintain control for transactions and - * restartability. This ensures that each call to {@link #read()} returns the - * ResultSet at the correct row, regardless of rollbacks or restarts. - *
- * - *- * {@link ExecutionContext}: The current row is returned as restart data, and - * when restored from that same data, the cursor is opened and the current row - * set to the value within the restart data. See - * {@link #setDriverSupportsAbsolute(boolean)} for improving restart - * performance. - *
- * - *- * Calling close on this {@link ItemStream} will cause all resources it is - * currently using to be freed. (Connection, ResultSet, etc). It is then illegal - * to call {@link #read()} again until it has been re-opened. - *
- * - *
- * Known limitation: when used with Derby
- * {@link #setVerifyCursorPosition(boolean)} needs to be false
- * because {@link ResultSet#getRow()} call used for cursor position verification
- * is not available for 'TYPE_FORWARD_ONLY' result sets.
+ * ResultSet.
*
null, in which case this method does nothing.
- * @throws SQLException
- *
- * @see org.springframework.jdbc.SQLWarningException
- */
- private void handleWarnings(PreparedStatement pstmt) throws SQLWarningException, SQLException {
- if (ignoreWarnings) {
- if (log.isDebugEnabled()) {
- SQLWarning warningToLog = pstmt.getWarnings();
- while (warningToLog != null) {
- log.debug("SQLWarning ignored: SQL state '" + warningToLog.getSQLState() + "', error code '"
- + warningToLog.getErrorCode() + "', message [" + warningToLog.getMessage() + "]");
- warningToLog = warningToLog.getNextWarning();
- }
- }
- }
- else {
- SQLWarning warnings = pstmt.getWarnings();
- if (warnings != null) {
- throw new SQLWarningException("Warning not ignored", warnings);
- }
- }
- }
-
- /**
- * Moves the cursor in the ResultSet to the position specified by the row
- * parameter by traversing the ResultSet.
- * @param row
- */
- private void moveCursorToRow(int row) {
- try {
- int count = 0;
- while (row != count && rs.next()) {
- count++;
- }
- }
- catch (SQLException se) {
- throw getExceptionTranslator().translate("Attempted to move ResultSet to last committed row", sql, se);
- }
- }
-
- /**
- * Gives the JDBC driver a hint as to the number of rows that should be
- * fetched from the database when more rows are needed for this
- * ResultSet object. If the fetch size specified is zero, the
- * JDBC driver ignores the value.
- *
- * @param fetchSize the number of rows to fetch
- * @see ResultSet#setFetchSize(int)
- */
- public void setFetchSize(int fetchSize) {
- this.fetchSize = fetchSize;
- }
-
- /**
- * Sets the limit for the maximum number of rows that any
- * ResultSet object can contain to the given number.
- *
- * @param maxRows the new max rows limit; zero means there is no limit
- * @see Statement#setMaxRows(int)
- */
- public void setMaxRows(int maxRows) {
- this.maxRows = maxRows;
- }
-
- /**
- * Sets the number of seconds the driver will wait for a
- * Statement object to execute to the given number of seconds.
- * If the limit is exceeded, an SQLException is thrown.
- *
- * @param queryTimeout seconds the new query timeout limit in seconds; zero
- * means there is no limit
- * @see Statement#setQueryTimeout(int)
- */
- public void setQueryTimeout(int queryTimeout) {
- this.queryTimeout = queryTimeout;
- }
-
- /**
- * Set whether SQLWarnings should be ignored (only logged) or exception
- * should be thrown.
- *
- * @param ignoreWarnings if TRUE, warnings are ignored
- */
- public void setIgnoreWarnings(boolean ignoreWarnings) {
- this.ignoreWarnings = ignoreWarnings;
- }
-
- /**
- * Allow verification of cursor position after current row is processed by
- * RowMapper or RowCallbackHandler. Default value is TRUE.
- *
- * @param verifyCursorPosition if true, cursor position is verified
- */
- public void setVerifyCursorPosition(boolean verifyCursorPosition) {
- this.verifyCursorPosition = verifyCursorPosition;
- }
-
/**
* Set the RowMapper to be used for all calls to read().
*
@@ -388,127 +97,56 @@ public class JdbcCursorItemReadertrue for JDBC drivers that supports ResultSet.absolute() as
- * it may improve performance, especially if a step fails while working with
- * a large data set.
+ * Assert that mandatory properties are set.
*
- * @see ResultSet#absolute(int)
- *
- * @param driverSupportsAbsolute false by default
+ * @throws IllegalArgumentException if either data source or sql properties
+ * not set.
*/
- public void setDriverSupportsAbsolute(boolean driverSupportsAbsolute) {
- this.driverSupportsAbsolute = driverSupportsAbsolute;
+ public void afterPropertiesSet() throws Exception {
+ super.afterPropertiesSet();
+ Assert.notNull(sql, "The SQL query must be provided");
+ Assert.notNull(rowMapper, "RowMapper must be provided");
}
- /**
- * Indicate whether the connection used for the cursor should be used by all other processing
- * thus sharing the same transaction. If this is set to false, which is the default, then the
- * cursor will be opened using in its connection and will not participate in any transactions
- * started for the rest of the step processing. If you set this flag to true then you must
- * wrap the DataSource in a {@link ExtendedConnectionDataSourceProxy} to prevent the
- * connection from being closed and released after each commit.
- *
- * When you set this option to true then the statement used to open the cursor
- * will be created with both 'READ_ONLY' and 'HOLD_CUSORS_OVER_COMMIT' options. This allows
- * holding the cursor open over transaction start and commits performed in the step processing.
- * To use this feature you need a database that supports this and a JDBC driver supporting
- * JDBC 3.0 or later.
- *
- * @param useSharedExtendedConnection false by default
- */
- public void setUseSharedExtendedConnection(boolean useSharedExtendedConnection) {
- this.useSharedExtendedConnection = useSharedExtendedConnection;
- }
- /**
- * Check the result set is in synch with the currentRow attribute. This is
- * important to ensure that the user hasn't modified the current row.
- */
- private void verifyCursorPosition(long expectedCurrentRow) throws SQLException {
- if (verifyCursorPosition) {
- if (expectedCurrentRow != this.rs.getRow()) {
- throw new InvalidDataAccessResourceUsageException("Unexpected cursor position change.");
+ protected void openCursor(Connection con) {
+ try {
+ if (isUseSharedExtendedConnection()) {
+ preparedStatement = con.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY,
+ ResultSet.HOLD_CURSORS_OVER_COMMIT);
}
+ else {
+ preparedStatement = con.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
+ }
+ applyStatementSettings(preparedStatement);
+ if (this.preparedStatementSetter != null) {
+ preparedStatementSetter.setValues(preparedStatement);
+ }
+ this.rs = preparedStatement.executeQuery();
+ handleWarnings(preparedStatement);
}
+ catch (SQLException se) {
+ close();
+ throw getExceptionTranslator().translate("Executing query", getSql(), se);
+ }
+
}
+
+ @SuppressWarnings("unchecked")
+ protected T readCursor(ResultSet rs, int currentRow) throws SQLException {
+ return (T) rowMapper.mapRow(rs, currentRow);
+ }
+
/**
* Close the cursor and database connection.
*/
- protected void doClose() throws Exception {
- initialized = false;
- JdbcUtils.closeResultSet(this.rs);
- rs = null;
+ protected void cleanupOnClose() throws Exception {
JdbcUtils.closeStatement(this.preparedStatement);
- if (useSharedExtendedConnection && dataSource instanceof ExtendedConnectionDataSourceProxy) {
- ((ExtendedConnectionDataSourceProxy)dataSource).stopCloseSuppression(this.con);
- if (!TransactionSynchronizationManager.isActualTransactionActive()) {
- DataSourceUtils.releaseConnection(con, dataSource);
- }
- }
- else {
- JdbcUtils.closeConnection(this.con);
- }
}
- /**
- * Execute the {@link #setSql(String)} query.
- */
- protected void doOpen() throws Exception {
- Assert.state(!initialized, "Stream is already initialized. Close before re-opening.");
- Assert.isNull(rs, "ResultSet still open! Close before re-opening.");
- executeQuery();
- initialized = true;
-
+ @Override
+ public String getSql() {
+ return this.sql;
}
-
- /**
- * Read next row and map it to item, verify cursor position if
- * {@link #setVerifyCursorPosition(boolean)} is true.
- */
- @SuppressWarnings("unchecked")
- protected T doRead() throws Exception {
- if (rs == null) {
- throw new ReaderNotOpenException("Reader must be open before it can be read.");
- }
-
- try {
- if (!rs.next()) {
- return null;
- }
- int currentRow = getCurrentItemCount();
- T item = (T) rowMapper.mapRow(rs, currentRow);
- verifyCursorPosition(currentRow);
- return item;
- }
- catch (SQLException se) {
- throw getExceptionTranslator().translate("Attempt to process next row failed", sql, se);
- }
- }
-
- /**
- * Use {@link ResultSet#absolute(int)} if possible, otherwise scroll by
- * calling {@link ResultSet#next()}.
- */
- protected void jumpToItem(int itemIndex) throws Exception {
- if (driverSupportsAbsolute) {
- try {
- rs.absolute(itemIndex);
- }
- catch (SQLException e) {
- // Driver does not support rs.absolute(int) revert to
- // traversing ResultSet
- log.warn("The JDBC driver does not appear to support ResultSet.absolute(). Consider"
- + " reverting to the default behavior setting the driverSupportsAbsolute to false", e);
-
- moveCursorToRow(itemIndex);
- }
- }
- else {
- moveCursorToRow(itemIndex);
- }
- }
-
}
diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/StoredProcedureItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/StoredProcedureItemReader.java
index 687d6cdf9..b9e482fb1 100644
--- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/StoredProcedureItemReader.java
+++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/StoredProcedureItemReader.java
@@ -18,52 +18,24 @@ package org.springframework.batch.item.database;
import java.sql.CallableStatement;
import java.sql.Connection;
-import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
-import java.sql.SQLWarning;
-import java.sql.Statement;
import java.sql.Types;
-import javax.sql.DataSource;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.springframework.batch.item.ExecutionContext;
-import org.springframework.batch.item.ItemStream;
-import org.springframework.batch.item.ReaderNotOpenException;
-import org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader;
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.dao.InvalidDataAccessApiUsageException;
-import org.springframework.dao.InvalidDataAccessResourceUsageException;
-import org.springframework.jdbc.SQLWarningException;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.core.metadata.CallMetaDataContext;
-import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.jdbc.object.SqlCall;
import org.springframework.jdbc.support.JdbcUtils;
-import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator;
-import org.springframework.jdbc.support.SQLExceptionTranslator;
-import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator;
-import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* - * Item reader that executes a stored procedure and then reads the returned cursor and continually - * retrieves the next row in the ResultSet. - *
- * - *
- * By default the procedure will be executed using a separate connection. The ResultSet for the cursor
- * is held open regardless of commits or roll backs in a surrounding transaction. Clients of this
- * reader are responsible for buffering the items in the case that they need to be re-presented on a
- * rollback. This buffering is handled by the step implementations provided and is only a concern for
- * anyone writing their own step implementations.
+ * Item reader implementation that executes a stored procedure and then reads the returned cursor
+ * and continually retrieves the next row in the ResultSet.
*
@@ -73,93 +45,28 @@ import org.springframework.util.ClassUtils; *
* *
- * There is an option ({@link #setUseSharedExtendedConnection(boolean)} that will share the connection
- * used for the cursor with the rest of the step processing. If you set this flag to true
- * then you must wrap the DataSource in a {@link ExtendedConnectionDataSourceProxy} to prevent the
- * connection from being closed and released after each commit performed as part of the step processing.
- * You must also use a JDBC driver supporting JDBC 3.0 or later since the cursor will be opened with the
- * additional option of 'HOLD_CUSORS_OVER_COMMIT' enabled.
- *
* Each call to {@link #read()} will call the provided RowMapper, passing in the - * ResultSet. There is currently no wrapping of the ResultSet to suppress calls - * to next(). However, if the RowMapper (mistakenly) increments the current row, - * the next call to read will verify that the current row is at the expected - * position and throw a DataAccessException if it is not. The reason for such strictness on the - * ResultSet is due to the need to maintain control for transactions and - * restartability. This ensures that each call to {@link #read()} returns the - * ResultSet at the correct row, regardless of rollbacks or restarts. - *
- * - *- * {@link ExecutionContext}: The current row is returned as restart data, and - * when restored from that same data, the cursor is opened and the current row - * set to the value within the restart data. See - * {@link #setDriverSupportsAbsolute(boolean)} for improving restart - * performance. - *
- * - *- * Calling close on this {@link ItemStream} will cause all resources it is - * currently using to be freed. (Connection, ResultSet, etc). It is then illegal - * to call {@link #read()} again until it has been re-opened. - *
- * - *
- * Known limitation: when used with Derby
- * {@link #setVerifyCursorPosition(boolean)} needs to be false
- * because {@link ResultSet#getRow()} call used for cursor position verification
- * is not available for 'TYPE_FORWARD_ONLY' result sets.
+ * ResultSet.
*
* This class is modeled after the similar JdbcCursorItemReader class.
*
null, in which case this method does nothing.
- * @throws SQLException
- *
- * @see org.springframework.jdbc.SQLWarningException
- */
- private void handleWarnings(PreparedStatement pstmt) throws SQLWarningException, SQLException {
- if (ignoreWarnings) {
- if (log.isDebugEnabled()) {
- SQLWarning warningToLog = pstmt.getWarnings();
- while (warningToLog != null) {
- log.debug("SQLWarning ignored: SQL state '" + warningToLog.getSQLState() + "', error code '"
- + warningToLog.getErrorCode() + "', message [" + warningToLog.getMessage() + "]");
- warningToLog = warningToLog.getNextWarning();
- }
- }
- }
- else {
- SQLWarning warnings = pstmt.getWarnings();
- if (warnings != null) {
- throw new SQLWarningException("Warning not ignored", warnings);
- }
- }
- }
-
- /**
- * Moves the cursor in the ResultSet to the position specified by the row
- * parameter by traversing the ResultSet.
- * @param row
- */
- private void moveCursorToRow(int row) {
- try {
- int count = 0;
- while (row != count && rs.next()) {
- count++;
- }
- }
- catch (SQLException se) {
- throw getExceptionTranslator().translate("Attempted to move ResultSet to last committed row", procedureName, se);
- }
- }
-
- /**
- * Gives the JDBC driver a hint as to the number of rows that should be
- * fetched from the database when more rows are needed for this
- * ResultSet object. If the fetch size specified is zero, the
- * JDBC driver ignores the value.
- *
- * @param fetchSize the number of rows to fetch
- * @see ResultSet#setFetchSize(int)
- */
- public void setFetchSize(int fetchSize) {
- this.fetchSize = fetchSize;
- }
-
- /**
- * Sets the limit for the maximum number of rows that any
- * ResultSet object can contain to the given number.
- *
- * @param maxRows the new max rows limit; zero means there is no limit
- * @see Statement#setMaxRows(int)
- */
- public void setMaxRows(int maxRows) {
- this.maxRows = maxRows;
- }
-
- /**
- * Sets the number of seconds the driver will wait for a
- * Statement object to execute to the given number of seconds.
- * If the limit is exceeded, an SQLException is thrown.
- *
- * @param queryTimeout seconds the new query timeout limit in seconds; zero
- * means there is no limit
- * @see Statement#setQueryTimeout(int)
- */
- public void setQueryTimeout(int queryTimeout) {
- this.queryTimeout = queryTimeout;
- }
-
- /**
- * Set whether SQLWarnings should be ignored (only logged) or exception
- * should be thrown.
- *
- * @param ignoreWarnings if TRUE, warnings are ignored
- */
- public void setIgnoreWarnings(boolean ignoreWarnings) {
- this.ignoreWarnings = ignoreWarnings;
- }
-
- /**
- * Allow verification of cursor position after current row is processed by
- * RowMapper or RowCallbackHandler. Default value is TRUE.
- *
- * @param verifyCursorPosition if true, cursor position is verified
- */
- public void setVerifyCursorPosition(boolean verifyCursorPosition) {
- this.verifyCursorPosition = verifyCursorPosition;
- }
-
/**
* Set the RowMapper to be used for all calls to read().
*
@@ -450,41 +108,6 @@ public class StoredProcedureItemReadertrue for JDBC drivers that supports ResultSet.absolute() as
- * it may improve performance, especially if a step fails while working with
- * a large data set.
- *
- * @see ResultSet#absolute(int)
- *
- * @param driverSupportsAbsolute false by default
- */
- public void setDriverSupportsAbsolute(boolean driverSupportsAbsolute) {
- this.driverSupportsAbsolute = driverSupportsAbsolute;
- }
-
- /**
- * Indicate whether the connection used for the cursor should be used by all other processing
- * thus sharing the same transaction. If this is set to false, which is the default, then the
- * cursor will be opened using in its connection and will not participate in any transactions
- * started for the rest of the step processing. If you set this flag to true then you must
- * wrap the DataSource in a {@link ExtendedConnectionDataSourceProxy} to prevent the
- * connection from being closed and released after each commit.
- *
- * When you set this option to true then the statement used to open the cursor
- * will be created with both 'READ_ONLY' and 'HOLD_CUSORS_OVER_COMMIT' options. This allows
- * holding the cursor open over transaction start and commits performed in the step processing.
- * To use this feature you need a database that supports this and a JDBC driver supporting
- * JDBC 3.0 or later.
- *
- * @param useSharedExtendedConnection false by default
- */
- public void setUseSharedExtendedConnection(boolean useSharedExtendedConnection) {
- this.useSharedExtendedConnection = useSharedExtendedConnection;
- }
-
/**
* Add one or more declared parameters. Used for configuring this operation when used in a
* bean factory. Each parameter will specify SQL type and (optionally) the parameter's name.
@@ -514,91 +137,109 @@ public class StoredProcedureItemReader