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