001/*
002 * Copyright 2002-2018 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.sql.Connection;
020import java.sql.SQLException;
021import java.sql.Statement;
022import javax.sql.DataSource;
023
024import org.apache.commons.logging.Log;
025import org.apache.commons.logging.LogFactory;
026
027import org.springframework.jdbc.CannotGetJdbcConnectionException;
028import org.springframework.transaction.TransactionDefinition;
029import org.springframework.transaction.support.TransactionSynchronizationAdapter;
030import org.springframework.transaction.support.TransactionSynchronizationManager;
031import org.springframework.util.Assert;
032
033/**
034 * Helper class that provides static methods for obtaining JDBC Connections from
035 * a {@link javax.sql.DataSource}. Includes special support for Spring-managed
036 * transactional Connections, e.g. managed by {@link DataSourceTransactionManager}
037 * or {@link org.springframework.transaction.jta.JtaTransactionManager}.
038 *
039 * <p>Used internally by Spring's {@link org.springframework.jdbc.core.JdbcTemplate},
040 * Spring's JDBC operation objects and the JDBC {@link DataSourceTransactionManager}.
041 * Can also be used directly in application code.
042 *
043 * @author Rod Johnson
044 * @author Juergen Hoeller
045 * @see #getConnection
046 * @see #releaseConnection
047 * @see DataSourceTransactionManager
048 * @see org.springframework.transaction.jta.JtaTransactionManager
049 * @see org.springframework.transaction.support.TransactionSynchronizationManager
050 */
051public abstract class DataSourceUtils {
052
053        /**
054         * Order value for TransactionSynchronization objects that clean up JDBC Connections.
055         */
056        public static final int CONNECTION_SYNCHRONIZATION_ORDER = 1000;
057
058        private static final Log logger = LogFactory.getLog(DataSourceUtils.class);
059
060
061        /**
062         * Obtain a Connection from the given DataSource. Translates SQLExceptions into
063         * the Spring hierarchy of unchecked generic data access exceptions, simplifying
064         * calling code and making any exception that is thrown more meaningful.
065         * <p>Is aware of a corresponding Connection bound to the current thread, for example
066         * when using {@link DataSourceTransactionManager}. Will bind a Connection to the
067         * thread if transaction synchronization is active, e.g. when running within a
068         * {@link org.springframework.transaction.jta.JtaTransactionManager JTA} transaction).
069         * @param dataSource the DataSource to obtain Connections from
070         * @return a JDBC Connection from the given DataSource
071         * @throws org.springframework.jdbc.CannotGetJdbcConnectionException
072         * if the attempt to get a Connection failed
073         * @see #releaseConnection
074         */
075        public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
076                try {
077                        return doGetConnection(dataSource);
078                }
079                catch (SQLException ex) {
080                        throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
081                }
082        }
083
084        /**
085         * Actually obtain a JDBC Connection from the given DataSource.
086         * Same as {@link #getConnection}, but throwing the original SQLException.
087         * <p>Is aware of a corresponding Connection bound to the current thread, for example
088         * when using {@link DataSourceTransactionManager}. Will bind a Connection to the thread
089         * if transaction synchronization is active (e.g. if in a JTA transaction).
090         * <p>Directly accessed by {@link TransactionAwareDataSourceProxy}.
091         * @param dataSource the DataSource to obtain Connections from
092         * @return a JDBC Connection from the given DataSource
093         * @throws SQLException if thrown by JDBC methods
094         * @see #doReleaseConnection
095         */
096        public static Connection doGetConnection(DataSource dataSource) throws SQLException {
097                Assert.notNull(dataSource, "No DataSource specified");
098
099                ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
100                if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
101                        conHolder.requested();
102                        if (!conHolder.hasConnection()) {
103                                logger.debug("Fetching resumed JDBC Connection from DataSource");
104                                conHolder.setConnection(dataSource.getConnection());
105                        }
106                        return conHolder.getConnection();
107                }
108                // Else we either got no holder or an empty thread-bound holder here.
109
110                logger.debug("Fetching JDBC Connection from DataSource");
111                Connection con = dataSource.getConnection();
112
113                if (TransactionSynchronizationManager.isSynchronizationActive()) {
114                        try {
115                                // Use same Connection for further JDBC actions within the transaction.
116                                // Thread-bound object will get removed by synchronization at transaction completion.
117                                ConnectionHolder holderToUse = conHolder;
118                                if (holderToUse == null) {
119                                        holderToUse = new ConnectionHolder(con);
120                                }
121                                else {
122                                        holderToUse.setConnection(con);
123                                }
124                                holderToUse.requested();
125                                TransactionSynchronizationManager.registerSynchronization(
126                                                new ConnectionSynchronization(holderToUse, dataSource));
127                                holderToUse.setSynchronizedWithTransaction(true);
128                                if (holderToUse != conHolder) {
129                                        TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
130                                }
131                        }
132                        catch (RuntimeException ex) {
133                                // Unexpected exception from external delegation call -> close Connection and rethrow.
134                                releaseConnection(con, dataSource);
135                                throw ex;
136                        }
137                }
138
139                return con;
140        }
141
142        /**
143         * Prepare the given Connection with the given transaction semantics.
144         * @param con the Connection to prepare
145         * @param definition the transaction definition to apply
146         * @return the previous isolation level, if any
147         * @throws SQLException if thrown by JDBC methods
148         * @see #resetConnectionAfterTransaction
149         */
150        public static Integer prepareConnectionForTransaction(Connection con, TransactionDefinition definition)
151                        throws SQLException {
152
153                Assert.notNull(con, "No Connection specified");
154
155                // Set read-only flag.
156                if (definition != null && definition.isReadOnly()) {
157                        try {
158                                if (logger.isDebugEnabled()) {
159                                        logger.debug("Setting JDBC Connection [" + con + "] read-only");
160                                }
161                                con.setReadOnly(true);
162                        }
163                        catch (SQLException ex) {
164                                Throwable exToCheck = ex;
165                                while (exToCheck != null) {
166                                        if (exToCheck.getClass().getSimpleName().contains("Timeout")) {
167                                                // Assume it's a connection timeout that would otherwise get lost: e.g. from JDBC 4.0
168                                                throw ex;
169                                        }
170                                        exToCheck = exToCheck.getCause();
171                                }
172                                // "read-only not supported" SQLException -> ignore, it's just a hint anyway
173                                logger.debug("Could not set JDBC Connection read-only", ex);
174                        }
175                        catch (RuntimeException ex) {
176                                Throwable exToCheck = ex;
177                                while (exToCheck != null) {
178                                        if (exToCheck.getClass().getSimpleName().contains("Timeout")) {
179                                                // Assume it's a connection timeout that would otherwise get lost: e.g. from Hibernate
180                                                throw ex;
181                                        }
182                                        exToCheck = exToCheck.getCause();
183                                }
184                                // "read-only not supported" UnsupportedOperationException -> ignore, it's just a hint anyway
185                                logger.debug("Could not set JDBC Connection read-only", ex);
186                        }
187                }
188
189                // Apply specific isolation level, if any.
190                Integer previousIsolationLevel = null;
191                if (definition != null && definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
192                        if (logger.isDebugEnabled()) {
193                                logger.debug("Changing isolation level of JDBC Connection [" + con + "] to " +
194                                                definition.getIsolationLevel());
195                        }
196                        int currentIsolation = con.getTransactionIsolation();
197                        if (currentIsolation != definition.getIsolationLevel()) {
198                                previousIsolationLevel = currentIsolation;
199                                con.setTransactionIsolation(definition.getIsolationLevel());
200                        }
201                }
202
203                return previousIsolationLevel;
204        }
205
206        /**
207         * Reset the given Connection after a transaction,
208         * regarding read-only flag and isolation level.
209         * @param con the Connection to reset
210         * @param previousIsolationLevel the isolation level to restore, if any
211         * @see #prepareConnectionForTransaction
212         */
213        public static void resetConnectionAfterTransaction(Connection con, Integer previousIsolationLevel) {
214                Assert.notNull(con, "No Connection specified");
215                try {
216                        // Reset transaction isolation to previous value, if changed for the transaction.
217                        if (previousIsolationLevel != null) {
218                                if (logger.isDebugEnabled()) {
219                                        logger.debug("Resetting isolation level of JDBC Connection [" +
220                                                        con + "] to " + previousIsolationLevel);
221                                }
222                                con.setTransactionIsolation(previousIsolationLevel);
223                        }
224
225                        // Reset read-only flag.
226                        if (con.isReadOnly()) {
227                                if (logger.isDebugEnabled()) {
228                                        logger.debug("Resetting read-only flag of JDBC Connection [" + con + "]");
229                                }
230                                con.setReadOnly(false);
231                        }
232                }
233                catch (Throwable ex) {
234                        logger.debug("Could not reset JDBC Connection after transaction", ex);
235                }
236        }
237
238        /**
239         * Determine whether the given JDBC Connection is transactional, that is,
240         * bound to the current thread by Spring's transaction facilities.
241         * @param con the Connection to check
242         * @param dataSource the DataSource that the Connection was obtained from
243         * (may be {@code null})
244         * @return whether the Connection is transactional
245         */
246        public static boolean isConnectionTransactional(Connection con, DataSource dataSource) {
247                if (dataSource == null) {
248                        return false;
249                }
250                ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
251                return (conHolder != null && connectionEquals(conHolder, con));
252        }
253
254        /**
255         * Apply the current transaction timeout, if any,
256         * to the given JDBC Statement object.
257         * @param stmt the JDBC Statement object
258         * @param dataSource the DataSource that the Connection was obtained from
259         * @throws SQLException if thrown by JDBC methods
260         * @see java.sql.Statement#setQueryTimeout
261         */
262        public static void applyTransactionTimeout(Statement stmt, DataSource dataSource) throws SQLException {
263                applyTimeout(stmt, dataSource, -1);
264        }
265
266        /**
267         * Apply the specified timeout - overridden by the current transaction timeout,
268         * if any - to the given JDBC Statement object.
269         * @param stmt the JDBC Statement object
270         * @param dataSource the DataSource that the Connection was obtained from
271         * @param timeout the timeout to apply (or 0 for no timeout outside of a transaction)
272         * @throws SQLException if thrown by JDBC methods
273         * @see java.sql.Statement#setQueryTimeout
274         */
275        public static void applyTimeout(Statement stmt, DataSource dataSource, int timeout) throws SQLException {
276                Assert.notNull(stmt, "No Statement specified");
277                ConnectionHolder holder = null;
278                if (dataSource != null) {
279                        holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
280                }
281                if (holder != null && holder.hasTimeout()) {
282                        // Remaining transaction timeout overrides specified value.
283                        stmt.setQueryTimeout(holder.getTimeToLiveInSeconds());
284                }
285                else if (timeout >= 0) {
286                        // No current transaction timeout -> apply specified value.
287                        stmt.setQueryTimeout(timeout);
288                }
289        }
290
291        /**
292         * Close the given Connection, obtained from the given DataSource,
293         * if it is not managed externally (that is, not bound to the thread).
294         * @param con the Connection to close if necessary
295         * (if this is {@code null}, the call will be ignored)
296         * @param dataSource the DataSource that the Connection was obtained from
297         * (may be {@code null})
298         * @see #getConnection
299         */
300        public static void releaseConnection(Connection con, DataSource dataSource) {
301                try {
302                        doReleaseConnection(con, dataSource);
303                }
304                catch (SQLException ex) {
305                        logger.debug("Could not close JDBC Connection", ex);
306                }
307                catch (Throwable ex) {
308                        logger.debug("Unexpected exception on closing JDBC Connection", ex);
309                }
310        }
311
312        /**
313         * Actually close the given Connection, obtained from the given DataSource.
314         * Same as {@link #releaseConnection}, but throwing the original SQLException.
315         * <p>Directly accessed by {@link TransactionAwareDataSourceProxy}.
316         * @param con the Connection to close if necessary
317         * (if this is {@code null}, the call will be ignored)
318         * @param dataSource the DataSource that the Connection was obtained from
319         * (may be {@code null})
320         * @throws SQLException if thrown by JDBC methods
321         * @see #doGetConnection
322         */
323        public static void doReleaseConnection(Connection con, DataSource dataSource) throws SQLException {
324                if (con == null) {
325                        return;
326                }
327                if (dataSource != null) {
328                        ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
329                        if (conHolder != null && connectionEquals(conHolder, con)) {
330                                // It's the transactional Connection: Don't close it.
331                                conHolder.released();
332                                return;
333                        }
334                }
335                doCloseConnection(con, dataSource);
336        }
337
338        /**
339         * Close the Connection, unless a {@link SmartDataSource} doesn't want us to.
340         * @param con the Connection to close if necessary
341         * @param dataSource the DataSource that the Connection was obtained from
342         * @throws SQLException if thrown by JDBC methods
343         * @see Connection#close()
344         * @see SmartDataSource#shouldClose(Connection)
345         */
346        public static void doCloseConnection(Connection con, DataSource dataSource) throws SQLException {
347                if (!(dataSource instanceof SmartDataSource) || ((SmartDataSource) dataSource).shouldClose(con)) {
348                        con.close();
349                }
350        }
351
352        /**
353         * Determine whether the given two Connections are equal, asking the target
354         * Connection in case of a proxy. Used to detect equality even if the
355         * user passed in a raw target Connection while the held one is a proxy.
356         * @param conHolder the ConnectionHolder for the held Connection (potentially a proxy)
357         * @param passedInCon the Connection passed-in by the user
358         * (potentially a target Connection without proxy)
359         * @return whether the given Connections are equal
360         * @see #getTargetConnection
361         */
362        private static boolean connectionEquals(ConnectionHolder conHolder, Connection passedInCon) {
363                if (!conHolder.hasConnection()) {
364                        return false;
365                }
366                Connection heldCon = conHolder.getConnection();
367                // Explicitly check for identity too: for Connection handles that do not implement
368                // "equals" properly, such as the ones Commons DBCP exposes).
369                return (heldCon == passedInCon || heldCon.equals(passedInCon) ||
370                                getTargetConnection(heldCon).equals(passedInCon));
371        }
372
373        /**
374         * Return the innermost target Connection of the given Connection. If the given
375         * Connection is a proxy, it will be unwrapped until a non-proxy Connection is
376         * found. Otherwise, the passed-in Connection will be returned as-is.
377         * @param con the Connection proxy to unwrap
378         * @return the innermost target Connection, or the passed-in one if no proxy
379         * @see ConnectionProxy#getTargetConnection()
380         */
381        public static Connection getTargetConnection(Connection con) {
382                Connection conToUse = con;
383                while (conToUse instanceof ConnectionProxy) {
384                        conToUse = ((ConnectionProxy) conToUse).getTargetConnection();
385                }
386                return conToUse;
387        }
388
389        /**
390         * Determine the connection synchronization order to use for the given
391         * DataSource. Decreased for every level of nesting that a DataSource
392         * has, checked through the level of DelegatingDataSource nesting.
393         * @param dataSource the DataSource to check
394         * @return the connection synchronization order to use
395         * @see #CONNECTION_SYNCHRONIZATION_ORDER
396         */
397        private static int getConnectionSynchronizationOrder(DataSource dataSource) {
398                int order = CONNECTION_SYNCHRONIZATION_ORDER;
399                DataSource currDs = dataSource;
400                while (currDs instanceof DelegatingDataSource) {
401                        order--;
402                        currDs = ((DelegatingDataSource) currDs).getTargetDataSource();
403                }
404                return order;
405        }
406
407
408        /**
409         * Callback for resource cleanup at the end of a non-native JDBC transaction
410         * (e.g. when participating in a JtaTransactionManager transaction).
411         * @see org.springframework.transaction.jta.JtaTransactionManager
412         */
413        private static class ConnectionSynchronization extends TransactionSynchronizationAdapter {
414
415                private final ConnectionHolder connectionHolder;
416
417                private final DataSource dataSource;
418
419                private int order;
420
421                private boolean holderActive = true;
422
423                public ConnectionSynchronization(ConnectionHolder connectionHolder, DataSource dataSource) {
424                        this.connectionHolder = connectionHolder;
425                        this.dataSource = dataSource;
426                        this.order = getConnectionSynchronizationOrder(dataSource);
427                }
428
429                @Override
430                public int getOrder() {
431                        return this.order;
432                }
433
434                @Override
435                public void suspend() {
436                        if (this.holderActive) {
437                                TransactionSynchronizationManager.unbindResource(this.dataSource);
438                                if (this.connectionHolder.hasConnection() && !this.connectionHolder.isOpen()) {
439                                        // Release Connection on suspend if the application doesn't keep
440                                        // a handle to it anymore. We will fetch a fresh Connection if the
441                                        // application accesses the ConnectionHolder again after resume,
442                                        // assuming that it will participate in the same transaction.
443                                        releaseConnection(this.connectionHolder.getConnection(), this.dataSource);
444                                        this.connectionHolder.setConnection(null);
445                                }
446                        }
447                }
448
449                @Override
450                public void resume() {
451                        if (this.holderActive) {
452                                TransactionSynchronizationManager.bindResource(this.dataSource, this.connectionHolder);
453                        }
454                }
455
456                @Override
457                public void beforeCompletion() {
458                        // Release Connection early if the holder is not open anymore
459                        // (that is, not used by another resource like a Hibernate Session
460                        // that has its own cleanup via transaction synchronization),
461                        // to avoid issues with strict JTA implementations that expect
462                        // the close call before transaction completion.
463                        if (!this.connectionHolder.isOpen()) {
464                                TransactionSynchronizationManager.unbindResource(this.dataSource);
465                                this.holderActive = false;
466                                if (this.connectionHolder.hasConnection()) {
467                                        releaseConnection(this.connectionHolder.getConnection(), this.dataSource);
468                                }
469                        }
470                }
471
472                @Override
473                public void afterCompletion(int status) {
474                        // If we haven't closed the Connection in beforeCompletion,
475                        // close it now. The holder might have been used for other
476                        // cleanup in the meantime, for example by a Hibernate Session.
477                        if (this.holderActive) {
478                                // The thread-bound ConnectionHolder might not be available anymore,
479                                // since afterCompletion might get called from a different thread.
480                                TransactionSynchronizationManager.unbindResourceIfPossible(this.dataSource);
481                                this.holderActive = false;
482                                if (this.connectionHolder.hasConnection()) {
483                                        releaseConnection(this.connectionHolder.getConnection(), this.dataSource);
484                                        // Reset the ConnectionHolder: It might remain bound to the thread.
485                                        this.connectionHolder.setConnection(null);
486                                }
487                        }
488                        this.connectionHolder.reset();
489                }
490        }
491
492}