KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > jdbc > datasource > DataSourceUtils


1 /*
2  * Copyright 2002-2007 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.springframework.jdbc.datasource;
18
19 import java.sql.Connection JavaDoc;
20 import java.sql.SQLException JavaDoc;
21 import java.sql.Statement JavaDoc;
22
23 import javax.sql.DataSource JavaDoc;
24
25 import org.apache.commons.logging.Log;
26 import org.apache.commons.logging.LogFactory;
27
28 import org.springframework.jdbc.CannotGetJdbcConnectionException;
29 import org.springframework.transaction.TransactionDefinition;
30 import org.springframework.transaction.support.TransactionSynchronizationAdapter;
31 import org.springframework.transaction.support.TransactionSynchronizationManager;
32 import org.springframework.util.Assert;
33  
34 /**
35  * Helper class that provides static methods for obtaining JDBC Connections from
36  * a {@link javax.sql.DataSource}. Includes special support for Spring-managed
37  * transactional Connections, e.g. managed by {@link DataSourceTransactionManager}
38  * or {@link org.springframework.transaction.jta.JtaTransactionManager}.
39  *
40  * <p>Used internally by Spring's {@link org.springframework.jdbc.core.JdbcTemplate},
41  * Spring's JDBC operation objects and the JDBC {@link DataSourceTransactionManager}.
42  * Can also be used directly in application code.
43  *
44  * @author Rod Johnson
45  * @author Juergen Hoeller
46  * @see #getConnection
47  * @see #releaseConnection
48  * @see DataSourceTransactionManager
49  * @see org.springframework.transaction.jta.JtaTransactionManager
50  * @see org.springframework.transaction.support.TransactionSynchronizationManager
51  */

52 public abstract class DataSourceUtils {
53
54     /**
55      * Order value for TransactionSynchronization objects that clean up
56      * JDBC Connections.
57      */

58     public static final int CONNECTION_SYNCHRONIZATION_ORDER = 1000;
59
60     private static final Log logger = LogFactory.getLog(DataSourceUtils.class);
61
62
63     /**
64      * Obtain a Connection from the given DataSource. Translates SQLExceptions into
65      * the Spring hierarchy of unchecked generic data access exceptions, simplifying
66      * calling code and making any exception that is thrown more meaningful.
67      * <p>Is aware of a corresponding Connection bound to the current thread, for example
68      * when using {@link DataSourceTransactionManager}. Will bind a Connection to the
69      * thread if transaction synchronization is active, e.g. when running within a
70      * {@link org.springframework.transaction.jta.JtaTransactionManager JTA} transaction).
71      * @param dataSource the DataSource to obtain Connections from
72      * @return a JDBC Connection from the given DataSource
73      * @throws org.springframework.jdbc.CannotGetJdbcConnectionException
74      * if the attempt to get a Connection failed
75      * @see #releaseConnection
76      */

77     public static Connection JavaDoc getConnection(DataSource JavaDoc dataSource) throws CannotGetJdbcConnectionException {
78         try {
79             return doGetConnection(dataSource);
80         }
81         catch (SQLException JavaDoc ex) {
82             throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
83         }
84     }
85
86     /**
87      * Actually obtain a JDBC Connection from the given DataSource.
88      * Same as {@link #getConnection}, but throwing the original SQLException.
89      * <p>Is aware of a corresponding Connection bound to the current thread, for example
90      * when using {@link DataSourceTransactionManager}. Will bind a Connection to the thread
91      * if transaction synchronization is active (e.g. if in a JTA transaction).
92      * <p>Directly accessed by {@link TransactionAwareDataSourceProxy}.
93      * @param dataSource the DataSource to obtain Connections from
94      * @return a JDBC Connection from the given DataSource
95      * @throws SQLException if thrown by JDBC methods
96      * @see #doReleaseConnection
97      */

98     public static Connection JavaDoc doGetConnection(DataSource JavaDoc dataSource) throws SQLException JavaDoc {
99         Assert.notNull(dataSource, "No DataSource specified");
100
101         ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
102         if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
103             conHolder.requested();
104             if (!conHolder.hasConnection()) {
105                 logger.debug("Fetching resumed JDBC Connection from DataSource");
106                 conHolder.setConnection(dataSource.getConnection());
107             }
108             return conHolder.getConnection();
109         }
110         // Else we either got no holder or an empty thread-bound holder here.
111

112         logger.debug("Fetching JDBC Connection from DataSource");
113         Connection JavaDoc con = dataSource.getConnection();
114
115         if (TransactionSynchronizationManager.isSynchronizationActive()) {
116             logger.debug("Registering transaction synchronization for JDBC Connection");
117             // Use same Connection for further JDBC actions within the transaction.
118
// Thread-bound object will get removed by synchronization at transaction completion.
119
ConnectionHolder holderToUse = conHolder;
120             if (holderToUse == null) {
121                 holderToUse = new ConnectionHolder(con);
122             }
123             else {
124                 holderToUse.setConnection(con);
125             }
126             holderToUse.requested();
127             TransactionSynchronizationManager.registerSynchronization(
128                     new ConnectionSynchronization(holderToUse, dataSource));
129             holderToUse.setSynchronizedWithTransaction(true);
130             if (holderToUse != conHolder) {
131                 TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
132             }
133         }
134
135         return con;
136     }
137
138     /**
139      * Prepare the given Connection with the given transaction semantics.
140      * @param con the Connection to prepare
141      * @param definition the transaction definition to apply
142      * @return the previous isolation level, if any
143      * @throws SQLException if thrown by JDBC methods
144      * @see #resetConnectionAfterTransaction
145      */

146     public static Integer JavaDoc prepareConnectionForTransaction(Connection JavaDoc con, TransactionDefinition definition)
147             throws SQLException JavaDoc {
148
149         Assert.notNull(con, "No Connection specified");
150
151         // Set read-only flag.
152
if (definition != null && definition.isReadOnly()) {
153             try {
154                 if (logger.isDebugEnabled()) {
155                     logger.debug("Setting JDBC Connection [" + con + "] read-only");
156                 }
157                 con.setReadOnly(true);
158             }
159             catch (Throwable JavaDoc ex) {
160                 // SQLException or UnsupportedOperationException
161
// -> ignore, it's just a hint anyway.
162
logger.debug("Could not set JDBC Connection read-only", ex);
163             }
164         }
165
166         // Apply specific isolation level, if any.
167
Integer JavaDoc previousIsolationLevel = null;
168         if (definition != null && definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
169             if (logger.isDebugEnabled()) {
170                 logger.debug("Changing isolation level of JDBC Connection [" + con + "] to " +
171                         definition.getIsolationLevel());
172             }
173             previousIsolationLevel = new Integer JavaDoc(con.getTransactionIsolation());
174             con.setTransactionIsolation(definition.getIsolationLevel());
175         }
176
177         return previousIsolationLevel;
178     }
179
180     /**
181      * Reset the given Connection after a transaction,
182      * regarding read-only flag and isolation level.
183      * @param con the Connection to reset
184      * @param previousIsolationLevel the isolation level to restore, if any
185      * @see #prepareConnectionForTransaction
186      */

187     public static void resetConnectionAfterTransaction(Connection JavaDoc con, Integer JavaDoc previousIsolationLevel) {
188         Assert.notNull(con, "No Connection specified");
189         try {
190             // Reset transaction isolation to previous value, if changed for the transaction.
191
if (previousIsolationLevel != null) {
192                 if (logger.isDebugEnabled()) {
193                     logger.debug("Resetting isolation level of JDBC Connection [" +
194                             con + "] to " + previousIsolationLevel);
195                 }
196                 con.setTransactionIsolation(previousIsolationLevel.intValue());
197             }
198
199             // Reset read-only flag.
200
if (con.isReadOnly()) {
201                 if (logger.isDebugEnabled()) {
202                     logger.debug("Resetting read-only flag of JDBC Connection [" + con + "]");
203                 }
204                 con.setReadOnly(false);
205             }
206         }
207         catch (Throwable JavaDoc ex) {
208             logger.debug("Could not reset JDBC Connection after transaction", ex);
209         }
210     }
211
212     /**
213      * Determine whether the given JDBC Connection is transactional, that is,
214      * bound to the current thread by Spring's transaction facilities.
215      * @param con the Connection to check
216      * @param dataSource the DataSource that the Connection was obtained from
217      * (may be <code>null</code>)
218      * @return whether the Connection is transactional
219      */

220     public static boolean isConnectionTransactional(Connection JavaDoc con, DataSource JavaDoc dataSource) {
221         if (dataSource == null) {
222             return false;
223         }
224         ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
225         return (conHolder != null && connectionEquals(conHolder, con));
226     }
227
228     /**
229      * Apply the current transaction timeout, if any,
230      * to the given JDBC Statement object.
231      * @param stmt the JDBC Statement object
232      * @param dataSource the DataSource that the Connection was obtained from
233      * @throws SQLException if thrown by JDBC methods
234      * @see java.sql.Statement#setQueryTimeout
235      */

236     public static void applyTransactionTimeout(Statement JavaDoc stmt, DataSource JavaDoc dataSource) throws SQLException JavaDoc {
237         applyTimeout(stmt, dataSource, 0);
238     }
239
240     /**
241      * Apply the specified timeout - overridden by the current transaction timeout,
242      * if any - to the given JDBC Statement object.
243      * @param stmt the JDBC Statement object
244      * @param dataSource the DataSource that the Connection was obtained from
245      * @param timeout the timeout to apply (or 0 for no timeout outside of a transaction)
246      * @throws SQLException if thrown by JDBC methods
247      * @see java.sql.Statement#setQueryTimeout
248      */

249     public static void applyTimeout(Statement JavaDoc stmt, DataSource JavaDoc dataSource, int timeout) throws SQLException JavaDoc {
250         Assert.notNull(stmt, "No Statement specified");
251         Assert.notNull(dataSource, "No DataSource specified");
252         ConnectionHolder holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
253         if (holder != null && holder.hasTimeout()) {
254             // Remaining transaction timeout overrides specified value.
255
stmt.setQueryTimeout(holder.getTimeToLiveInSeconds());
256         }
257         else if (timeout > 0) {
258             // No current transaction timeout -> apply specified value.
259
stmt.setQueryTimeout(timeout);
260         }
261     }
262
263     /**
264      * Close the given Connection, obtained from the given DataSource,
265      * if it is not managed externally (that is, not bound to the thread).
266      * @param con the Connection to close if necessary
267      * (if this is <code>null</code>, the call will be ignored)
268      * @param dataSource the DataSource that the Connection was obtained from
269      * (may be <code>null</code>)
270      * @see #getConnection
271      */

272     public static void releaseConnection(Connection JavaDoc con, DataSource JavaDoc dataSource) {
273         try {
274             doReleaseConnection(con, dataSource);
275         }
276         catch (SQLException JavaDoc ex) {
277             logger.debug("Could not close JDBC Connection", ex);
278         }
279         catch (Throwable JavaDoc ex) {
280             logger.debug("Unexpected exception on closing JDBC Connection", ex);
281         }
282     }
283
284     /**
285      * Actually close the given Connection, obtained from the given DataSource.
286      * Same as {@link #releaseConnection}, but throwing the original SQLException.
287      * <p>Directly accessed by {@link TransactionAwareDataSourceProxy}.
288      * @param con the Connection to close if necessary
289      * (if this is <code>null</code>, the call will be ignored)
290      * @param dataSource the DataSource that the Connection was obtained from
291      * (may be <code>null</code>)
292      * @throws SQLException if thrown by JDBC methods
293      * @see #doGetConnection
294      */

295     public static void doReleaseConnection(Connection JavaDoc con, DataSource JavaDoc dataSource) throws SQLException JavaDoc {
296         if (con == null) {
297             return;
298         }
299
300         if (dataSource != null) {
301             ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
302             if (conHolder != null && connectionEquals(conHolder, con)) {
303                 // It's the transactional Connection: Don't close it.
304
conHolder.released();
305                 return;
306             }
307         }
308
309         // Leave the Connection open only if the DataSource is our
310
// special SmartDataSoruce and it wants the Connection left open.
311
if (!(dataSource instanceof SmartDataSource) || ((SmartDataSource) dataSource).shouldClose(con)) {
312             logger.debug("Returning JDBC Connection to DataSource");
313             con.close();
314         }
315     }
316
317     /**
318      * Determine whether the given two Connections are equal, asking the target
319      * Connection in case of a proxy. Used to detect equality even if the
320      * user passed in a raw target Connection while the held one is a proxy.
321      * @param conHolder the ConnectionHolder for the held Connection (potentially a proxy)
322      * @param passedInCon the Connection passed-in by the user
323      * (potentially a target Connection without proxy)
324      * @return whether the given Connections are equal
325      * @see #getTargetConnection
326      */

327     private static boolean connectionEquals(ConnectionHolder conHolder, Connection JavaDoc passedInCon) {
328         if (!conHolder.hasConnection()) {
329             return false;
330         }
331         Connection JavaDoc heldCon = conHolder.getConnection();
332         // Explicitly check for identity too: for Connection handles that do not implement
333
// "equals" properly, such as the ones Commons DBCP exposes).
334
return (heldCon == passedInCon || heldCon.equals(passedInCon) ||
335                 getTargetConnection(heldCon).equals(passedInCon));
336     }
337
338     /**
339      * Return the innermost target Connection of the given Connection. If the given
340      * Connection is a proxy, it will be unwrapped until a non-proxy Connection is
341      * found. Else, the passed-in Connection will be returned as-is.
342      * @param con the Connection proxy to unwrap
343      * @return the innermost target Connection, or the passed-in one if no proxy
344      * @see ConnectionProxy#getTargetConnection
345      */

346     public static Connection JavaDoc getTargetConnection(Connection JavaDoc con) {
347         Connection JavaDoc conToUse = con;
348         while (conToUse instanceof ConnectionProxy) {
349             conToUse = ((ConnectionProxy) conToUse).getTargetConnection();
350         }
351         return conToUse;
352     }
353
354     /**
355      * Determine the connection synchronization order to use for the given
356      * DataSource. Decreased for every level of nesting that a DataSource
357      * has, checked through the level of DelegatingDataSource nesting.
358      * @param dataSource the DataSource to check
359      * @return the connection synchronization order to use
360      * @see #CONNECTION_SYNCHRONIZATION_ORDER
361      */

362     private static int getConnectionSynchronizationOrder(DataSource JavaDoc dataSource) {
363         int order = CONNECTION_SYNCHRONIZATION_ORDER;
364         DataSource JavaDoc currDs = dataSource;
365         while (currDs instanceof DelegatingDataSource) {
366             order--;
367             currDs = ((DelegatingDataSource) currDs).getTargetDataSource();
368         }
369         return order;
370     }
371
372
373     /**
374      * Callback for resource cleanup at the end of a non-native JDBC transaction
375      * (e.g. when participating in a JtaTransactionManager transaction).
376      * @see org.springframework.transaction.jta.JtaTransactionManager
377      */

378     private static class ConnectionSynchronization extends TransactionSynchronizationAdapter {
379
380         private final ConnectionHolder connectionHolder;
381
382         private final DataSource JavaDoc dataSource;
383
384         private int order;
385
386         private boolean holderActive = true;
387
388         public ConnectionSynchronization(ConnectionHolder connectionHolder, DataSource JavaDoc dataSource) {
389             this.connectionHolder = connectionHolder;
390             this.dataSource = dataSource;
391             this.order = getConnectionSynchronizationOrder(dataSource);
392         }
393
394         public int getOrder() {
395             return this.order;
396         }
397
398         public void suspend() {
399             if (this.holderActive) {
400                 TransactionSynchronizationManager.unbindResource(this.dataSource);
401                 if (this.connectionHolder.hasConnection() && !this.connectionHolder.isOpen()) {
402                     // Release Connection on suspend if the application doesn't keep
403
// a handle to it anymore. We will fetch a fresh Connection if the
404
// application accesses the ConnectionHolder again after resume,
405
// assuming that it will participate in the same transaction.
406
releaseConnection(this.connectionHolder.getConnection(), this.dataSource);
407                     this.connectionHolder.setConnection(null);
408                 }
409             }
410         }
411
412         public void resume() {
413             if (this.holderActive) {
414                 TransactionSynchronizationManager.bindResource(this.dataSource, this.connectionHolder);
415             }
416         }
417
418         public void beforeCompletion() {
419             // Release Connection early if the holder is not open anymore
420
// (that is, not used by another resource like a Hibernate Session
421
// that has its own cleanup via transaction synchronization),
422
// to avoid issues with strict JTA implementations that expect
423
// the close call before transaction completion.
424
if (!this.connectionHolder.isOpen()) {
425                 TransactionSynchronizationManager.unbindResource(this.dataSource);
426                 this.holderActive = false;
427                 if (this.connectionHolder.hasConnection()) {
428                     releaseConnection(this.connectionHolder.getConnection(), this.dataSource);
429                 }
430             }
431         }
432
433         public void afterCompletion(int status) {
434             // If we haven't closed the Connection in beforeCompletion,
435
// close it now. The holder might have been used for other
436
// cleanup in the meantime, for example by a Hibernate Session.
437
if (this.holderActive) {
438                 // The thread-bound ConnectionHolder might not be available anymore,
439
// since afterCompletion might get called from a different thread.
440
if (TransactionSynchronizationManager.hasResource(this.dataSource)) {
441                     TransactionSynchronizationManager.unbindResource(this.dataSource);
442                 }
443                 this.holderActive = false;
444                 if (this.connectionHolder.hasConnection()) {
445                     releaseConnection(this.connectionHolder.getConnection(), this.dataSource);
446                     // Reset the ConnectionHolder: It might remain bound to the thread.
447
this.connectionHolder.setConnection(null);
448                 }
449                 this.connectionHolder.reset();
450             }
451         }
452     }
453
454 }
455
Popular Tags