1 /*
2 * Copyright 2002-2008 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;
20 import java.sql.SQLException;
21 import java.sql.Statement;
22
23 import javax.sql.DataSource;
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 getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
78 try {
79 return doGetConnection(dataSource);
80 }
81 catch (SQLException 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 doGetConnection(DataSource dataSource) throws SQLException {
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 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 prepareConnectionForTransaction(Connection con, TransactionDefinition definition)
147 throws SQLException {
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 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 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(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 con, Integer 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 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 con, DataSource 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 stmt, DataSource dataSource) throws SQLException {
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 stmt, DataSource dataSource, int timeout) throws SQLException {
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 con, DataSource dataSource) {
273 try {
274 doReleaseConnection(con, dataSource);
275 }
276 catch (SQLException ex) {
277 logger.debug("Could not close JDBC Connection", ex);
278 }
279 catch (Throwable 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 con, DataSource dataSource) throws SQLException {
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 passedInCon) {
328 if (!conHolder.hasConnection()) {
329 return false;
330 }
331 Connection 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. Otherwise, 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 getTargetConnection(Connection con) {
347 Connection 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 dataSource) {
363 int order = CONNECTION_SYNCHRONIZATION_ORDER;
364 DataSource 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 dataSource;
383
384 private int order;
385
386 private boolean holderActive = true;
387
388 public ConnectionSynchronization(ConnectionHolder connectionHolder, DataSource 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 TransactionSynchronizationManager.unbindResourceIfPossible(this.dataSource);
441 this.holderActive = false;
442 if (this.connectionHolder.hasConnection()) {
443 releaseConnection(this.connectionHolder.getConnection(), this.dataSource);
444 // Reset the ConnectionHolder: It might remain bound to the thread.
445 this.connectionHolder.setConnection(null);
446 }
447 }
448 this.connectionHolder.reset();
449 }
450 }
451
452 }