001/*
002 * Copyright 2002-2019 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 *      https://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
017package org.springframework.jdbc.datasource;
018
019import java.lang.reflect.InvocationHandler;
020import java.lang.reflect.InvocationTargetException;
021import java.lang.reflect.Method;
022import java.lang.reflect.Proxy;
023import java.sql.Connection;
024import java.sql.SQLException;
025import java.sql.Statement;
026
027import javax.sql.DataSource;
028
029import org.springframework.lang.Nullable;
030import org.springframework.transaction.support.TransactionSynchronizationManager;
031
032/**
033 * Proxy for a target JDBC {@link javax.sql.DataSource}, adding awareness of
034 * Spring-managed transactions. Similar to a transactional JNDI DataSource
035 * as provided by a Java EE server.
036 *
037 * <p>Data access code that should remain unaware of Spring's data access support
038 * can work with this proxy to seamlessly participate in Spring-managed transactions.
039 * Note that the transaction manager, for example {@link DataSourceTransactionManager},
040 * still needs to work with the underlying DataSource, <i>not</i> with this proxy.
041 *
042 * <p><b>Make sure that TransactionAwareDataSourceProxy is the outermost DataSource
043 * of a chain of DataSource proxies/adapters.</b> TransactionAwareDataSourceProxy
044 * can delegate either directly to the target connection pool or to some
045 * intermediary proxy/adapter like {@link LazyConnectionDataSourceProxy} or
046 * {@link UserCredentialsDataSourceAdapter}.
047 *
048 * <p>Delegates to {@link DataSourceUtils} for automatically participating in
049 * thread-bound transactions, for example managed by {@link DataSourceTransactionManager}.
050 * {@code getConnection} calls and {@code close} calls on returned Connections
051 * will behave properly within a transaction, i.e. always operate on the transactional
052 * Connection. If not within a transaction, normal DataSource behavior applies.
053 *
054 * <p>This proxy allows data access code to work with the plain JDBC API and still
055 * participate in Spring-managed transactions, similar to JDBC code in a Java EE/JTA
056 * environment. However, if possible, use Spring's DataSourceUtils, JdbcTemplate or
057 * JDBC operation objects to get transaction participation even without a proxy for
058 * the target DataSource, avoiding the need to define such a proxy in the first place.
059 *
060 * <p>As a further effect, using a transaction-aware DataSource will apply remaining
061 * transaction timeouts to all created JDBC (Prepared/Callable)Statement. This means
062 * that all operations performed through standard JDBC will automatically participate
063 * in Spring-managed transaction timeouts.
064 *
065 * <p><b>NOTE:</b> This DataSource proxy needs to return wrapped Connections (which
066 * implement the {@link ConnectionProxy} interface) in order to handle close calls
067 * properly. Use {@link Connection#unwrap} to retrieve the native JDBC Connection.
068 *
069 * @author Juergen Hoeller
070 * @since 1.1
071 * @see javax.sql.DataSource#getConnection()
072 * @see java.sql.Connection#close()
073 * @see DataSourceUtils#doGetConnection
074 * @see DataSourceUtils#applyTransactionTimeout
075 * @see DataSourceUtils#doReleaseConnection
076 */
077public class TransactionAwareDataSourceProxy extends DelegatingDataSource {
078
079        private boolean reobtainTransactionalConnections = false;
080
081
082        /**
083         * Create a new TransactionAwareDataSourceProxy.
084         * @see #setTargetDataSource
085         */
086        public TransactionAwareDataSourceProxy() {
087        }
088
089        /**
090         * Create a new TransactionAwareDataSourceProxy.
091         * @param targetDataSource the target DataSource
092         */
093        public TransactionAwareDataSourceProxy(DataSource targetDataSource) {
094                super(targetDataSource);
095        }
096
097        /**
098         * Specify whether to reobtain the target Connection for each operation
099         * performed within a transaction.
100         * <p>The default is "false". Specify "true" to reobtain transactional
101         * Connections for every call on the Connection proxy; this is advisable
102         * on JBoss if you hold on to a Connection handle across transaction boundaries.
103         * <p>The effect of this setting is similar to the
104         * "hibernate.connection.release_mode" value "after_statement".
105         */
106        public void setReobtainTransactionalConnections(boolean reobtainTransactionalConnections) {
107                this.reobtainTransactionalConnections = reobtainTransactionalConnections;
108        }
109
110
111        /**
112         * Delegates to DataSourceUtils for automatically participating in Spring-managed
113         * transactions. Throws the original SQLException, if any.
114         * <p>The returned Connection handle implements the ConnectionProxy interface,
115         * allowing to retrieve the underlying target Connection.
116         * @return a transactional Connection if any, a new one else
117         * @see DataSourceUtils#doGetConnection
118         * @see ConnectionProxy#getTargetConnection
119         */
120        @Override
121        public Connection getConnection() throws SQLException {
122                return getTransactionAwareConnectionProxy(obtainTargetDataSource());
123        }
124
125        /**
126         * Wraps the given Connection with a proxy that delegates every method call to it
127         * but delegates {@code close()} calls to DataSourceUtils.
128         * @param targetDataSource the DataSource that the Connection came from
129         * @return the wrapped Connection
130         * @see java.sql.Connection#close()
131         * @see DataSourceUtils#doReleaseConnection
132         */
133        protected Connection getTransactionAwareConnectionProxy(DataSource targetDataSource) {
134                return (Connection) Proxy.newProxyInstance(
135                                ConnectionProxy.class.getClassLoader(),
136                                new Class<?>[] {ConnectionProxy.class},
137                                new TransactionAwareInvocationHandler(targetDataSource));
138        }
139
140        /**
141         * Determine whether to obtain a fixed target Connection for the proxy
142         * or to reobtain the target Connection for each operation.
143         * <p>The default implementation returns {@code true} for all
144         * standard cases. This can be overridden through the
145         * {@link #setReobtainTransactionalConnections "reobtainTransactionalConnections"}
146         * flag, which enforces a non-fixed target Connection within an active transaction.
147         * Note that non-transactional access will always use a fixed Connection.
148         * @param targetDataSource the target DataSource
149         */
150        protected boolean shouldObtainFixedConnection(DataSource targetDataSource) {
151                return (!TransactionSynchronizationManager.isSynchronizationActive() ||
152                                !this.reobtainTransactionalConnections);
153        }
154
155
156        /**
157         * Invocation handler that delegates close calls on JDBC Connections
158         * to DataSourceUtils for being aware of thread-bound transactions.
159         */
160        private class TransactionAwareInvocationHandler implements InvocationHandler {
161
162                private final DataSource targetDataSource;
163
164                @Nullable
165                private Connection target;
166
167                private boolean closed = false;
168
169                public TransactionAwareInvocationHandler(DataSource targetDataSource) {
170                        this.targetDataSource = targetDataSource;
171                }
172
173                @Override
174                @Nullable
175                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
176                        // Invocation on ConnectionProxy interface coming in...
177
178                        if (method.getName().equals("equals")) {
179                                // Only considered as equal when proxies are identical.
180                                return (proxy == args[0]);
181                        }
182                        else if (method.getName().equals("hashCode")) {
183                                // Use hashCode of Connection proxy.
184                                return System.identityHashCode(proxy);
185                        }
186                        else if (method.getName().equals("toString")) {
187                                // Allow for differentiating between the proxy and the raw Connection.
188                                StringBuilder sb = new StringBuilder("Transaction-aware proxy for target Connection ");
189                                if (this.target != null) {
190                                        sb.append("[").append(this.target.toString()).append("]");
191                                }
192                                else {
193                                        sb.append(" from DataSource [").append(this.targetDataSource).append("]");
194                                }
195                                return sb.toString();
196                        }
197                        else if (method.getName().equals("unwrap")) {
198                                if (((Class<?>) args[0]).isInstance(proxy)) {
199                                        return proxy;
200                                }
201                        }
202                        else if (method.getName().equals("isWrapperFor")) {
203                                if (((Class<?>) args[0]).isInstance(proxy)) {
204                                        return true;
205                                }
206                        }
207                        else if (method.getName().equals("close")) {
208                                // Handle close method: only close if not within a transaction.
209                                DataSourceUtils.doReleaseConnection(this.target, this.targetDataSource);
210                                this.closed = true;
211                                return null;
212                        }
213                        else if (method.getName().equals("isClosed")) {
214                                return this.closed;
215                        }
216
217                        if (this.target == null) {
218                                if (method.getName().equals("getWarnings") || method.getName().equals("clearWarnings")) {
219                                        // Avoid creation of target Connection on pre-close cleanup (e.g. Hibernate Session)
220                                        return null;
221                                }
222                                if (this.closed) {
223                                        throw new SQLException("Connection handle already closed");
224                                }
225                                if (shouldObtainFixedConnection(this.targetDataSource)) {
226                                        this.target = DataSourceUtils.doGetConnection(this.targetDataSource);
227                                }
228                        }
229                        Connection actualTarget = this.target;
230                        if (actualTarget == null) {
231                                actualTarget = DataSourceUtils.doGetConnection(this.targetDataSource);
232                        }
233
234                        if (method.getName().equals("getTargetConnection")) {
235                                // Handle getTargetConnection method: return underlying Connection.
236                                return actualTarget;
237                        }
238
239                        // Invoke method on target Connection.
240                        try {
241                                Object retVal = method.invoke(actualTarget, args);
242
243                                // If return value is a Statement, apply transaction timeout.
244                                // Applies to createStatement, prepareStatement, prepareCall.
245                                if (retVal instanceof Statement) {
246                                        DataSourceUtils.applyTransactionTimeout((Statement) retVal, this.targetDataSource);
247                                }
248
249                                return retVal;
250                        }
251                        catch (InvocationTargetException ex) {
252                                throw ex.getTargetException();
253                        }
254                        finally {
255                                if (actualTarget != this.target) {
256                                        DataSourceUtils.doReleaseConnection(actualTarget, this.targetDataSource);
257                                }
258                        }
259                }
260        }
261
262}