REOPENED - BATCH-970: JdbcCursorItemReader executes outside of main transaction

added non-recursive implementations of javax.sql.Wrapper methods to the ExtendedConnectionDataSourceProxy to keep both JDK 5 and 6 happy
This commit is contained in:
robokaso
2009-01-05 11:02:51 +00:00
parent c668a1f5db
commit 3f85b58dbb
2 changed files with 250 additions and 92 deletions

View File

@@ -26,47 +26,60 @@ import java.sql.SQLException;
import javax.sql.DataSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.datasource.ConnectionProxy;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.jdbc.datasource.SmartDataSource;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
/**
* Implementation of {@link SmartDataSource} that is capable of keeping a single JDBC Connection
* which is NOT closed after each use even if {@link Connection#close()} is called.
* Implementation of {@link SmartDataSource} that is capable of keeping a single
* JDBC Connection which is NOT closed after each use even if
* {@link Connection#close()} is called.
*
* The connection can be kept open over multiple transactions when used together with any of Spring's
* {@link org.springframework.transaction.PlatformTransactionManager} implementations.
* The connection can be kept open over multiple transactions when used together
* with any of Spring's
* {@link org.springframework.transaction.PlatformTransactionManager}
* implementations.
*
* <p>Loosely based on the SingleConnectionDataSource implementation in Spring Core. Intended
* to be used with the {@link JdbcCursorItemReader} to provide a connection that remains
* open across transaction boundaries, It remains open for the life of the cursor, and can be
* shared with the main transaction of the rest of the step processing.
*
* <p>Once close suppression has been turned on for a connection, it will be returned for the first
* {@link #getConnection()} call. Any subsequent calls to {@link #getConnection()} will retrieve a
* new connection from the wrapped {@link DataSource} until the {@link DataSourceUtils} queries
* whether the connection should be closed or not by calling {@link #shouldClose(Connection)} for
* the close-suppressed {@link Connection}. At that point the cycle starts over again, and the next
* {@link #getConnection()} call will have the {@link Connection} that is being close-suppressed
* returned. This allows the use of the close-suppressed {@link Connection} to be the main
* {@link Connection} for an extended data access process. The close suppression is turned off by
* calling {@link #stopCloseSuppression(Connection)}.
* <p>
* Loosely based on the SingleConnectionDataSource implementation in Spring
* Core. Intended to be used with the {@link JdbcCursorItemReader} to provide a
* connection that remains open across transaction boundaries, It remains open
* for the life of the cursor, and can be shared with the main transaction of
* the rest of the step processing.
*
* <p>This class is not multi-threading capable.
* <p>
* Once close suppression has been turned on for a connection, it will be
* returned for the first {@link #getConnection()} call. Any subsequent calls to
* {@link #getConnection()} will retrieve a new connection from the wrapped
* {@link DataSource} until the {@link DataSourceUtils} queries whether the
* connection should be closed or not by calling
* {@link #shouldClose(Connection)} for the close-suppressed {@link Connection}.
* At that point the cycle starts over again, and the next
* {@link #getConnection()} call will have the {@link Connection} that is being
* close-suppressed returned. This allows the use of the close-suppressed
* {@link Connection} to be the main {@link Connection} for an extended data
* access process. The close suppression is turned off by calling
* {@link #stopCloseSuppression(Connection)}.
*
* <p>
* This class is not multi-threading capable.
*
* <p>
* The connection returned will be a close-suppressing proxy instead of the
* physical {@link Connection}. Be aware that you will not be able to cast this
* to a native <code>OracleConnection</code> or the like anymore; you need to
* use a {@link org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor}.
*
* <p>The connection returned will be a close-suppressing proxy instead of the physical
* {@link Connection}. Be aware that you will not be able to cast this to a native
* <code>OracleConnection</code> or the like anymore; you need to use a
* {@link org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor}.
*
* @author Thomas Risberg
* @see #getConnection()
* @see java.sql.Connection#close()
* @see DataSourceUtils#releaseConnection
* @see org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor
*/
public class ExtendedConnectionDataSourceProxy implements SmartDataSource {
public class ExtendedConnectionDataSourceProxy implements SmartDataSource, InitializingBean {
/** Provided DataSource */
private DataSource dataSource;
@@ -87,7 +100,8 @@ public class ExtendedConnectionDataSourceProxy implements SmartDataSource {
}
/**
* Constructor that takes as a parameter with the {&link DataSource} to be wrapped.
* Constructor that takes as a parameter with the {&link DataSource} to be
* wrapped.
*/
public ExtendedConnectionDataSourceProxy(DataSource dataSource) {
this.dataSource = dataSource;
@@ -114,9 +128,11 @@ public class ExtendedConnectionDataSourceProxy implements SmartDataSource {
}
/**
* Return the status of close suppression being activated for a given {@link Connection}
* Return the status of close suppression being activated for a given
* {@link Connection}
*
* @param connection the {@link Connection} that the close suppression status is requested for
* @param connection the {@link Connection} that the close suppression
* status is requested for
* @return true or false
*/
public boolean isCloseSuppressionActive(Connection connection) {
@@ -125,23 +141,25 @@ public class ExtendedConnectionDataSourceProxy implements SmartDataSource {
}
return connection.equals(closeSuppressedConnection) ? true : false;
}
/**
*
* @param connection the {@link Connection} that close suppression is requested for
* @param connection the {@link Connection} that close suppression is
* requested for
*/
public void startCloseSuppression(Connection connection) {
synchronized (this.connectionMonitor) {
closeSuppressedConnection = connection;
if(TransactionSynchronizationManager.isActualTransactionActive()) {
if (TransactionSynchronizationManager.isActualTransactionActive()) {
borrowedConnection = true;
}
}
}
/**
*
* @param connection the {@link Connection} that close suppression should be turned off for
* @param connection the {@link Connection} that close suppression should be
* turned off for
*/
public void stopCloseSuppression(Connection connection) {
synchronized (this.connectionMonitor) {
@@ -149,7 +167,7 @@ public class ExtendedConnectionDataSourceProxy implements SmartDataSource {
borrowedConnection = false;
}
}
public Connection getConnection() throws SQLException {
synchronized (this.connectionMonitor) {
return initConnection(null, null);
@@ -204,29 +222,27 @@ public class ExtendedConnectionDataSourceProxy implements SmartDataSource {
}
/**
* Wrap the given Connection with a proxy that delegates every method call to it
* but suppresses close calls.
* Wrap the given Connection with a proxy that delegates every method call
* to it but suppresses close calls.
* @param target the original Connection to wrap
* @return the wrapped Connection
*/
protected Connection getCloseSuppressingConnectionProxy(Connection target) {
return (Connection) Proxy.newProxyInstance(
ConnectionProxy.class.getClassLoader(),
new Class[] {ConnectionProxy.class},
new CloseSuppressingInvocationHandler(target, this));
return (Connection) Proxy.newProxyInstance(ConnectionProxy.class.getClassLoader(),
new Class[] { ConnectionProxy.class }, new CloseSuppressingInvocationHandler(target, this));
}
/**
* Invocation handler that suppresses close calls on JDBC Connections until the associated instance of the
* ExtendedConnectionDataSourceProxy determines the connection should actually be closed.
* Invocation handler that suppresses close calls on JDBC Connections until
* the associated instance of the ExtendedConnectionDataSourceProxy
* determines the connection should actually be closed.
*/
private static class CloseSuppressingInvocationHandler implements InvocationHandler {
private final Connection target;
private final ExtendedConnectionDataSourceProxy dataSource;
public CloseSuppressingInvocationHandler(Connection target, ExtendedConnectionDataSourceProxy dataSource) {
this.dataSource = dataSource;
this.target = target;
@@ -244,8 +260,9 @@ public class ExtendedConnectionDataSourceProxy implements SmartDataSource {
return new Integer(System.identityHashCode(proxy));
}
else if (method.getName().equals("close")) {
// Handle close method: don't pass the call on if we are suppressing close calls.
if (dataSource.completeCloseCall((Connection)proxy)) {
// Handle close method: don't pass the call on if we are
// suppressing close calls.
if (dataSource.completeCloseCall((Connection) proxy)) {
return null;
}
else {
@@ -254,7 +271,8 @@ public class ExtendedConnectionDataSourceProxy implements SmartDataSource {
}
}
else if (method.getName().equals("getTargetConnection")) {
// Handle getTargetConnection method: return underlying Connection.
// Handle getTargetConnection method: return underlying
// Connection.
return this.target;
}
@@ -267,5 +285,39 @@ public class ExtendedConnectionDataSourceProxy implements SmartDataSource {
}
}
}
/**
* Performs only a 'shallow' non-recursive check of self's and delegate's
* class to retain Java 5 compatibility.
*/
public boolean isWrapperFor(Class<?> iface) throws SQLException {
if (iface.isAssignableFrom(SmartDataSource.class) || iface.isAssignableFrom(dataSource.getClass())) {
return true;
}
return false;
}
/**
* Returns either self or delegate (in this order) if one of them can be
* cast to supplied parameter class. Does *not* support recursive unwrapping
* of the delegate to retain Java 5 compatibility.
*/
public <T> T unwrap(Class<T> iface) throws SQLException {
if (iface.isAssignableFrom(SmartDataSource.class)) {
@SuppressWarnings("unchecked")
T casted = (T) this;
return casted;
}
else if (iface.isAssignableFrom(dataSource.getClass())) {
@SuppressWarnings("unchecked")
T casted = (T) dataSource;
return casted;
}
throw new SQLException("Unsupported class " + iface.getSimpleName());
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(dataSource);
}
}