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}