001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.jdbc.datasource;
018:
019: import java.lang.reflect.InvocationHandler;
020: import java.lang.reflect.InvocationTargetException;
021: import java.lang.reflect.Method;
022: import java.lang.reflect.Proxy;
023: import java.sql.Connection;
024: import java.sql.SQLException;
025: import java.sql.Statement;
026:
027: import javax.sql.DataSource;
028:
029: import org.springframework.util.Assert;
030:
031: /**
032: * Proxy for a target JDBC {@link javax.sql.DataSource}, adding awareness of
033: * Spring-managed transactions. Similar to a transactional JNDI DataSource
034: * as provided by a J2EE server.
035: *
036: * <p>Data access code that should remain unaware of Spring's data access support
037: * can work with this proxy to seamlessly participate in Spring-managed transactions.
038: * Note that the transaction manager, for example {@link DataSourceTransactionManager},
039: * still needs to work with the underlying DataSource, <i>not</i> with this proxy.
040: *
041: * <p><b>Make sure that TransactionAwareDataSourceProxy is the outermost DataSource
042: * of a chain of DataSource proxies/adapters.</b> TransactionAwareDataSourceProxy
043: * can delegate either directly to the target connection pool or to some
044: * intermediary proxy/adapter like {@link LazyConnectionDataSourceProxy} or
045: * {@link UserCredentialsDataSourceAdapter}.
046: *
047: * <p>Delegates to {@link DataSourceUtils} for automatically participating in
048: * thread-bound transactions, for example managed by {@link DataSourceTransactionManager}.
049: * <code>getConnection</code> calls and <code>close</code> calls on returned Connections
050: * will behave properly within a transaction, i.e. always operate on the transactional
051: * Connection. If not within a transaction, normal DataSource behavior applies.
052: *
053: * <p>This proxy allows data access code to work with the plain JDBC API and still
054: * participate in Spring-managed transactions, similar to JDBC code in a J2EE/JTA
055: * environment. However, if possible, use Spring's DataSourceUtils, JdbcTemplate or
056: * JDBC operation objects to get transaction participation even without a proxy for
057: * the target DataSource, avoiding the need to define such a proxy in the first place.
058: *
059: * <p>As a further effect, using a transaction-aware DataSource will apply remaining
060: * transaction timeouts to all created JDBC (Prepared/Callable)Statement. This means
061: * that all operations performed through standard JDBC will automatically participate
062: * in Spring-managed transaction timeouts.
063: *
064: * <p><b>NOTE:</b> This DataSource proxy needs to return wrapped Connections
065: * (which implement the {@link ConnectionProxy} interface) in order to handle
066: * close calls properly. Therefore, the returned Connections cannot be cast
067: * to a native JDBC Connection type like OracleConnection or to a connection
068: * pool implementation type. Use a corresponding
069: * {@link org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor}
070: * to retrieve the native JDBC Connection.
071: *
072: * @author Juergen Hoeller
073: * @since 1.1
074: * @see javax.sql.DataSource#getConnection()
075: * @see java.sql.Connection#close()
076: * @see DataSourceUtils#doGetConnection
077: * @see DataSourceUtils#applyTransactionTimeout
078: * @see DataSourceUtils#doReleaseConnection
079: */
080: public class TransactionAwareDataSourceProxy extends
081: DelegatingDataSource {
082:
083: /**
084: * Create a new TransactionAwareDataSourceProxy.
085: * @see #setTargetDataSource
086: */
087: public TransactionAwareDataSourceProxy() {
088: }
089:
090: /**
091: * Create a new TransactionAwareDataSourceProxy.
092: * @param targetDataSource the target DataSource
093: */
094: public TransactionAwareDataSourceProxy(DataSource targetDataSource) {
095: super (targetDataSource);
096: }
097:
098: /**
099: * Delegate to DataSourceUtils for automatically participating in Spring-managed
100: * transactions. Throws the original SQLException, if any.
101: * <p>The returned Connection handle implements the ConnectionProxy interface,
102: * allowing to retrieve the underlying target Connection.
103: * @return a transactional Connection if any, a new one else
104: * @see DataSourceUtils#doGetConnection
105: * @see ConnectionProxy#getTargetConnection
106: */
107: public Connection getConnection() throws SQLException {
108: Assert.state(getTargetDataSource() != null,
109: "'targetDataSource' is required");
110: Connection con = DataSourceUtils
111: .doGetConnection(getTargetDataSource());
112: return getTransactionAwareConnectionProxy(con,
113: getTargetDataSource());
114: }
115:
116: /**
117: * Wrap the given Connection with a proxy that delegates every method call to it
118: * but delegates <code>close</code> calls to DataSourceUtils.
119: * @param target the original Connection to wrap
120: * @param dataSource DataSource that the Connection came from
121: * @return the wrapped Connection
122: * @see java.sql.Connection#close()
123: * @see DataSourceUtils#doReleaseConnection
124: */
125: protected Connection getTransactionAwareConnectionProxy(
126: Connection target, DataSource dataSource) {
127: return (Connection) Proxy.newProxyInstance(
128: ConnectionProxy.class.getClassLoader(),
129: new Class[] { ConnectionProxy.class },
130: new TransactionAwareInvocationHandler(target,
131: dataSource));
132: }
133:
134: /**
135: * Invocation handler that delegates close calls on JDBC Connections
136: * to DataSourceUtils for being aware of thread-bound transactions.
137: */
138: private static class TransactionAwareInvocationHandler implements
139: InvocationHandler {
140:
141: private final Connection target;
142:
143: private final DataSource dataSource;
144:
145: public TransactionAwareInvocationHandler(Connection target,
146: DataSource dataSource) {
147: this .target = target;
148: this .dataSource = dataSource;
149: }
150:
151: public Object invoke(Object proxy, Method method, Object[] args)
152: throws Throwable {
153: // Invocation on ConnectionProxy interface coming in...
154:
155: if (method.getName().equals("getTargetConnection")) {
156: // Handle getTargetConnection method: return underlying Connection.
157: return this .target;
158: } else if (method.getName().equals("equals")) {
159: // Only considered as equal when proxies are identical.
160: return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
161: } else if (method.getName().equals("hashCode")) {
162: // Use hashCode of Connection proxy.
163: return new Integer(hashCode());
164: } else if (method.getName().equals("toString")) {
165: // Allow for differentiating between the proxy and the raw Connection.
166: return "Transaction-aware proxy for target Connection ["
167: + this .target.toString() + "]";
168: } else if (method.getName().equals("close")) {
169: // Handle close method: only close if not within a transaction.
170: DataSourceUtils.doReleaseConnection(this .target,
171: this .dataSource);
172: return null;
173: }
174:
175: // Invoke method on target Connection.
176: try {
177: Object retVal = method.invoke(this .target, args);
178:
179: // If return value is a Statement, apply transaction timeout.
180: // Applies to createStatement, prepareStatement, prepareCall.
181: if (retVal instanceof Statement) {
182: DataSourceUtils.applyTransactionTimeout(
183: (Statement) retVal, this .dataSource);
184: }
185:
186: return retVal;
187: } catch (InvocationTargetException ex) {
188: throw ex.getTargetException();
189: }
190: }
191: }
192:
193: }
|