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 39959afcb..1d4f62cb9 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 @@ -67,39 +67,29 @@ import org.springframework.util.ClassUtils; * position and throw a DataAccessException if it is not. This means that, in * theory, a RowMapper could read ahead, as long as it returns the row back to * the correct position before returning. The reason for such strictness on the - * ResultSet is due to the need to maintain control for transactions, - * restartability and skippability. This ensures that each call to - * {@link #read()} returns the ResultSet at the correct line, regardless of - * rollbacks, restarts, or skips. + * 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 line, 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. Two values are stored: the current - * line being processed and the number of lines that have been skipped. + * set to the value within the restart data. See + * {@link #setDriverSupportsAbsolute(boolean)} for improving restart + * performance. *
* ** Transactions: The same ResultSet is held open regardless of commits or roll * backs in a surrounding transaction. This means that when such a transaction - * is committed, the input source is notified through the {@link #mark()} and + * is committed, the reader is notified through the {@link #mark()} and * {@link #reset()} so that it can save it's current row number. Later, if the * transaction is rolled back, the current row can be moved back to the same row * number as it was on when commit was called. *
* *- * Calling skip will indicate that a record is bad and should not be - * re-presented to the user if the transaction is rolled back. For example, if - * row 2 is read in, and found to be bad, calling skip will inform the - * {@link ItemReader}. If reading is then continued, and a rollback is - * necessary because of an error on output, the input source will be returned to - * row 1. Calling read while on row 1 will move the current row to 3, not 2, - * because 2 has been marked as skipped. - *
- * - ** 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 opened. @@ -154,6 +144,8 @@ public class JdbcCursorItemReader extends ExecutionContextUserSupport implements private boolean saveState = false; + private boolean driverSupportsAbsolute = false; + private BufferredResultSetReader bufferredReader; public JdbcCursorItemReader() { @@ -183,10 +175,7 @@ public class JdbcCursorItemReader extends ExecutionContextUserSupport implements /** * Increment the cursor to the next row, validating the cursor position and - * passing the resultset to the RowMapper. If read has not been called on - * this instance before, the cursor will be opened. If there are skipped - * records for this commit scope, an internal list of skipped records will - * be checked to ensure that only a valid row is given to the mapper. + * passing the ResultSet to the RowMapper. * * @returns Object returned by RowMapper * @throws DataAccessException @@ -218,7 +207,7 @@ public class JdbcCursorItemReader extends ExecutionContextUserSupport implements } /** - * Close this input source. The ResultSet, Statement and Connection created + * Close this item reader. The ResultSet, Statement and Connection created * will be closed. This must be called or the connection and cursor will be * held open indefinitely! * @@ -285,10 +274,11 @@ public class JdbcCursorItemReader extends ExecutionContextUserSupport implements } } - /* - * Return the exception translator for this instance.
Creates a default
- * SQLErrorCodeSQLExceptionTranslator for the specified DataSource if none
- * is set.
+ /**
+ * Return the exception translator for this instance.
+ *
+ * Creates a default SQLErrorCodeSQLExceptionTranslator for the specified
+ * DataSource if none is set.
*/
protected SQLExceptionTranslator getExceptionTranslator() {
if (exceptionTranslator == null) {
@@ -302,7 +292,7 @@ public class JdbcCursorItemReader extends ExecutionContextUserSupport implements
return exceptionTranslator;
}
- /*
+ /**
* Throw a SQLWarningException if we're not ignoring warnings, else log the
* warnings (at debug level).
*
@@ -337,10 +327,11 @@ public class JdbcCursorItemReader extends ExecutionContextUserSupport implements
}
}
- /*
- * (non-Javadoc)
- *
- * @see org.springframework.batch.item.stream.ItemStreamAdapter#restoreFrom(org.springframework.batch.item.ExecutionContext)
+ /**
+ * Execute the {@link #setSql(String)} query and move to the saved position
+ * in case of restart. If {@link #setDriverSupportsAbsolute(boolean)} is set
+ * to true the reader will attempt to use {@link ResultSet#absolute(int)} to
+ * move the cursor and fall back on ResultSet traversal in case of failure.
*/
public void open(ExecutionContext context) {
Assert.state(!initialized, "Stream is already initialized. Close before re-opening.");
@@ -351,24 +342,49 @@ public class JdbcCursorItemReader extends ExecutionContextUserSupport implements
int processedRowCount = 0;
if (context.containsKey(getKey(CURRENT_PROCESSED_ROW))) {
- try {
- processedRowCount = Long.valueOf(context.getLong(getKey(CURRENT_PROCESSED_ROW))).intValue();
- int count = 0;
- while (rs.next()) {
- count++;
- if (count == processedRowCount) {
- break;
- }
+ processedRowCount = Long.valueOf(context.getLong(getKey(CURRENT_PROCESSED_ROW))).intValue();
+
+ if (driverSupportsAbsolute) {
+ try {
+ rs.absolute(processedRowCount);
+ }
+ 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(processedRowCount);
}
}
- catch (SQLException se) {
- throw getExceptionTranslator().translate("Attempted to move ResultSet to last committed row", sql, se);
+ else {
+ moveCursorToRow(processedRowCount);
}
}
bufferredReader = new BufferredResultSetReader(rs, mapper, processedRowCount);
}
+ /**
+ * 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 (rs.next()) {
+ count++;
+ if (count == row) {
+ break;
+ }
+ }
+ }
+ 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
@@ -437,8 +453,8 @@ public class JdbcCursorItemReader extends ExecutionContextUserSupport implements
}
/**
- * Set the sql statement to be used when creating the cursor. This statement
- * should be a complete and valid Sql statement, as it will be run directly
+ * Set the SQL statement to be used when creating the cursor. This statement
+ * should be a complete and valid SQL statement, as it will be run directly
* without any modification.
*
* @param sql
@@ -467,6 +483,21 @@ public class JdbcCursorItemReader extends ExecutionContextUserSupport implements
this.saveState = saveState;
}
+ /**
+ * 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;
+ }
+
private class BufferredResultSetReader implements ItemReader {
private ResultSet rs;
@@ -528,10 +559,10 @@ public class JdbcCursorItemReader extends ExecutionContextUserSupport implements
currentIndex = INITIAL_POSITION;
}
- // 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.
+ /**
+ * 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()) {
diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/JdbcCursorItemReaderCommonTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/JdbcCursorItemReaderCommonTests.java
index acd9ce86d..06277bc66 100644
--- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/JdbcCursorItemReaderCommonTests.java
+++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/JdbcCursorItemReaderCommonTests.java
@@ -5,20 +5,29 @@ import org.springframework.batch.item.ItemReader;
public class JdbcCursorItemReaderCommonTests extends CommonDatabaseItemStreamItemReaderTests {
protected ItemReader getItemReader() throws Exception {
-
+
JdbcCursorItemReader result = new JdbcCursorItemReader();
result.setDataSource(getDataSource());
result.setSql("select ID, NAME, VALUE from T_FOOS");
result.setIgnoreWarnings(true);
result.setVerifyCursorPosition(true);
-
+
result.setMapper(new FooRowMapper());
result.setFetchSize(10);
result.setMaxRows(100);
result.setQueryTimeout(1000);
result.setSaveState(true);
+ result.setDriverSupportsAbsolute(false);
return result;
}
+ public void testRestartWithDriverSupportsAbsolute() throws Exception {
+ tested = getItemReader();
+ ((JdbcCursorItemReader) tested).setDriverSupportsAbsolute(true);
+ testedAsStream().open(executionContext);
+
+ testRestart();
+ }
+
}