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