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}