001/* 002 * Copyright 2002-2020 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.springframework.beans.factory.InitializingBean; 026import org.springframework.lang.Nullable; 027import org.springframework.transaction.CannotCreateTransactionException; 028import org.springframework.transaction.TransactionDefinition; 029import org.springframework.transaction.TransactionSystemException; 030import org.springframework.transaction.support.AbstractPlatformTransactionManager; 031import org.springframework.transaction.support.DefaultTransactionStatus; 032import org.springframework.transaction.support.ResourceTransactionManager; 033import org.springframework.transaction.support.TransactionSynchronizationManager; 034import org.springframework.transaction.support.TransactionSynchronizationUtils; 035import org.springframework.util.Assert; 036 037/** 038 * {@link org.springframework.transaction.PlatformTransactionManager} 039 * implementation for a single JDBC {@link javax.sql.DataSource}. This class is 040 * capable of working in any environment with any JDBC driver, as long as the setup 041 * uses a {@code javax.sql.DataSource} as its {@code Connection} factory mechanism. 042 * Binds a JDBC Connection from the specified DataSource to the current thread, 043 * potentially allowing for one thread-bound Connection per DataSource. 044 * 045 * <p><b>Note: The DataSource that this transaction manager operates on needs 046 * to return independent Connections.</b> The Connections may come from a pool 047 * (the typical case), but the DataSource must not return thread-scoped / 048 * request-scoped Connections or the like. This transaction manager will 049 * associate Connections with thread-bound transactions itself, according 050 * to the specified propagation behavior. It assumes that a separate, 051 * independent Connection can be obtained even during an ongoing transaction. 052 * 053 * <p>Application code is required to retrieve the JDBC Connection via 054 * {@link DataSourceUtils#getConnection(DataSource)} instead of a standard 055 * Java EE-style {@link DataSource#getConnection()} call. Spring classes such as 056 * {@link org.springframework.jdbc.core.JdbcTemplate} use this strategy implicitly. 057 * If not used in combination with this transaction manager, the 058 * {@link DataSourceUtils} lookup strategy behaves exactly like the native 059 * DataSource lookup; it can thus be used in a portable fashion. 060 * 061 * <p>Alternatively, you can allow application code to work with the standard 062 * Java EE-style lookup pattern {@link DataSource#getConnection()}, for example for 063 * legacy code that is not aware of Spring at all. In that case, define a 064 * {@link TransactionAwareDataSourceProxy} for your target DataSource, and pass 065 * that proxy DataSource to your DAOs, which will automatically participate in 066 * Spring-managed transactions when accessing it. 067 * 068 * <p>Supports custom isolation levels, and timeouts which get applied as 069 * appropriate JDBC statement timeouts. To support the latter, application code 070 * must either use {@link org.springframework.jdbc.core.JdbcTemplate}, call 071 * {@link DataSourceUtils#applyTransactionTimeout} for each created JDBC Statement, 072 * or go through a {@link TransactionAwareDataSourceProxy} which will create 073 * timeout-aware JDBC Connections and Statements automatically. 074 * 075 * <p>Consider defining a {@link LazyConnectionDataSourceProxy} for your target 076 * DataSource, pointing both this transaction manager and your DAOs to it. 077 * This will lead to optimized handling of "empty" transactions, i.e. of transactions 078 * without any JDBC statements executed. A LazyConnectionDataSourceProxy will not fetch 079 * an actual JDBC Connection from the target DataSource until a Statement gets executed, 080 * lazily applying the specified transaction settings to the target Connection. 081 * 082 * <p>This transaction manager supports nested transactions via the JDBC 3.0 083 * {@link java.sql.Savepoint} mechanism. The 084 * {@link #setNestedTransactionAllowed "nestedTransactionAllowed"} flag defaults 085 * to "true", since nested transactions will work without restrictions on JDBC 086 * drivers that support savepoints (such as the Oracle JDBC driver). 087 * 088 * <p>This transaction manager can be used as a replacement for the 089 * {@link org.springframework.transaction.jta.JtaTransactionManager} in the single 090 * resource case, as it does not require a container that supports JTA, typically 091 * in combination with a locally defined JDBC DataSource (e.g. an Apache Commons 092 * DBCP connection pool). Switching between this local strategy and a JTA 093 * environment is just a matter of configuration! 094 * 095 * <p>As of 4.3.4, this transaction manager triggers flush callbacks on registered 096 * transaction synchronizations (if synchronization is generally active), assuming 097 * resources operating on the underlying JDBC {@code Connection}. This allows for 098 * setup analogous to {@code JtaTransactionManager}, in particular with respect to 099 * lazily registered ORM resources (e.g. a Hibernate {@code Session}). 100 * 101 * @author Juergen Hoeller 102 * @since 02.05.2003 103 * @see #setNestedTransactionAllowed 104 * @see java.sql.Savepoint 105 * @see DataSourceUtils#getConnection(javax.sql.DataSource) 106 * @see DataSourceUtils#applyTransactionTimeout 107 * @see DataSourceUtils#releaseConnection 108 * @see TransactionAwareDataSourceProxy 109 * @see LazyConnectionDataSourceProxy 110 * @see org.springframework.jdbc.core.JdbcTemplate 111 */ 112@SuppressWarnings("serial") 113public class DataSourceTransactionManager extends AbstractPlatformTransactionManager 114 implements ResourceTransactionManager, InitializingBean { 115 116 @Nullable 117 private DataSource dataSource; 118 119 private boolean enforceReadOnly = false; 120 121 122 /** 123 * Create a new DataSourceTransactionManager instance. 124 * A DataSource has to be set to be able to use it. 125 * @see #setDataSource 126 */ 127 public DataSourceTransactionManager() { 128 setNestedTransactionAllowed(true); 129 } 130 131 /** 132 * Create a new DataSourceTransactionManager instance. 133 * @param dataSource the JDBC DataSource to manage transactions for 134 */ 135 public DataSourceTransactionManager(DataSource dataSource) { 136 this(); 137 setDataSource(dataSource); 138 afterPropertiesSet(); 139 } 140 141 142 /** 143 * Set the JDBC DataSource that this instance should manage transactions for. 144 * <p>This will typically be a locally defined DataSource, for example an 145 * Apache Commons DBCP connection pool. Alternatively, you can also drive 146 * transactions for a non-XA J2EE DataSource fetched from JNDI. For an XA 147 * DataSource, use JtaTransactionManager. 148 * <p>The DataSource specified here should be the target DataSource to manage 149 * transactions for, not a TransactionAwareDataSourceProxy. Only data access 150 * code may work with TransactionAwareDataSourceProxy, while the transaction 151 * manager needs to work on the underlying target DataSource. If there's 152 * nevertheless a TransactionAwareDataSourceProxy passed in, it will be 153 * unwrapped to extract its target DataSource. 154 * <p><b>The DataSource passed in here needs to return independent Connections.</b> 155 * The Connections may come from a pool (the typical case), but the DataSource 156 * must not return thread-scoped / request-scoped Connections or the like. 157 * @see TransactionAwareDataSourceProxy 158 * @see org.springframework.transaction.jta.JtaTransactionManager 159 */ 160 public void setDataSource(@Nullable DataSource dataSource) { 161 if (dataSource instanceof TransactionAwareDataSourceProxy) { 162 // If we got a TransactionAwareDataSourceProxy, we need to perform transactions 163 // for its underlying target DataSource, else data access code won't see 164 // properly exposed transactions (i.e. transactions for the target DataSource). 165 this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource(); 166 } 167 else { 168 this.dataSource = dataSource; 169 } 170 } 171 172 /** 173 * Return the JDBC DataSource that this instance manages transactions for. 174 */ 175 @Nullable 176 public DataSource getDataSource() { 177 return this.dataSource; 178 } 179 180 /** 181 * Obtain the DataSource for actual use. 182 * @return the DataSource (never {@code null}) 183 * @throws IllegalStateException in case of no DataSource set 184 * @since 5.0 185 */ 186 protected DataSource obtainDataSource() { 187 DataSource dataSource = getDataSource(); 188 Assert.state(dataSource != null, "No DataSource set"); 189 return dataSource; 190 } 191 192 /** 193 * Specify whether to enforce the read-only nature of a transaction 194 * (as indicated by {@link TransactionDefinition#isReadOnly()} 195 * through an explicit statement on the transactional connection: 196 * "SET TRANSACTION READ ONLY" as understood by Oracle, MySQL and Postgres. 197 * <p>The exact treatment, including any SQL statement executed on the connection, 198 * can be customized through {@link #prepareTransactionalConnection}. 199 * <p>This mode of read-only handling goes beyond the {@link Connection#setReadOnly} 200 * hint that Spring applies by default. In contrast to that standard JDBC hint, 201 * "SET TRANSACTION READ ONLY" enforces an isolation-level-like connection mode 202 * where data manipulation statements are strictly disallowed. Also, on Oracle, 203 * this read-only mode provides read consistency for the entire transaction. 204 * <p>Note that older Oracle JDBC drivers (9i, 10g) used to enforce this read-only 205 * mode even for {@code Connection.setReadOnly(true}. However, with recent drivers, 206 * this strong enforcement needs to be applied explicitly, e.g. through this flag. 207 * @since 4.3.7 208 * @see #prepareTransactionalConnection 209 */ 210 public void setEnforceReadOnly(boolean enforceReadOnly) { 211 this.enforceReadOnly = enforceReadOnly; 212 } 213 214 /** 215 * Return whether to enforce the read-only nature of a transaction 216 * through an explicit statement on the transactional connection. 217 * @since 4.3.7 218 * @see #setEnforceReadOnly 219 */ 220 public boolean isEnforceReadOnly() { 221 return this.enforceReadOnly; 222 } 223 224 @Override 225 public void afterPropertiesSet() { 226 if (getDataSource() == null) { 227 throw new IllegalArgumentException("Property 'dataSource' is required"); 228 } 229 } 230 231 232 @Override 233 public Object getResourceFactory() { 234 return obtainDataSource(); 235 } 236 237 @Override 238 protected Object doGetTransaction() { 239 DataSourceTransactionObject txObject = new DataSourceTransactionObject(); 240 txObject.setSavepointAllowed(isNestedTransactionAllowed()); 241 ConnectionHolder conHolder = 242 (ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource()); 243 txObject.setConnectionHolder(conHolder, false); 244 return txObject; 245 } 246 247 @Override 248 protected boolean isExistingTransaction(Object transaction) { 249 DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; 250 return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive()); 251 } 252 253 @Override 254 protected void doBegin(Object transaction, TransactionDefinition definition) { 255 DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; 256 Connection con = null; 257 258 try { 259 if (!txObject.hasConnectionHolder() || 260 txObject.getConnectionHolder().isSynchronizedWithTransaction()) { 261 Connection newCon = obtainDataSource().getConnection(); 262 if (logger.isDebugEnabled()) { 263 logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction"); 264 } 265 txObject.setConnectionHolder(new ConnectionHolder(newCon), true); 266 } 267 268 txObject.getConnectionHolder().setSynchronizedWithTransaction(true); 269 con = txObject.getConnectionHolder().getConnection(); 270 271 Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition); 272 txObject.setPreviousIsolationLevel(previousIsolationLevel); 273 txObject.setReadOnly(definition.isReadOnly()); 274 275 // Switch to manual commit if necessary. This is very expensive in some JDBC drivers, 276 // so we don't want to do it unnecessarily (for example if we've explicitly 277 // configured the connection pool to set it already). 278 if (con.getAutoCommit()) { 279 txObject.setMustRestoreAutoCommit(true); 280 if (logger.isDebugEnabled()) { 281 logger.debug("Switching JDBC Connection [" + con + "] to manual commit"); 282 } 283 con.setAutoCommit(false); 284 } 285 286 prepareTransactionalConnection(con, definition); 287 txObject.getConnectionHolder().setTransactionActive(true); 288 289 int timeout = determineTimeout(definition);>290 if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { 291 txObject.getConnectionHolder().setTimeoutInSeconds(timeout); 292 } 293 294 // Bind the connection holder to the thread. 295 if (txObject.isNewConnectionHolder()) { 296 TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder()); 297 } 298 } 299 300 catch (Throwable ex) { 301 if (txObject.isNewConnectionHolder()) { 302 DataSourceUtils.releaseConnection(con, obtainDataSource()); 303 txObject.setConnectionHolder(null, false); 304 } 305 throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex); 306 } 307 } 308 309 @Override 310 protected Object doSuspend(Object transaction) { 311 DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; 312 txObject.setConnectionHolder(null); 313 return TransactionSynchronizationManager.unbindResource(obtainDataSource()); 314 } 315 316 @Override 317 protected void doResume(@Nullable Object transaction, Object suspendedResources) { 318 TransactionSynchronizationManager.bindResource(obtainDataSource(), suspendedResources); 319 } 320 321 @Override 322 protected void doCommit(DefaultTransactionStatus status) { 323 DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction(); 324 Connection con = txObject.getConnectionHolder().getConnection(); 325 if (status.isDebug()) { 326 logger.debug("Committing JDBC transaction on Connection [" + con + "]"); 327 } 328 try { 329 con.commit(); 330 } 331 catch (SQLException ex) { 332 throw new TransactionSystemException("Could not commit JDBC transaction", ex); 333 } 334 } 335 336 @Override 337 protected void doRollback(DefaultTransactionStatus status) { 338 DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction(); 339 Connection con = txObject.getConnectionHolder().getConnection(); 340 if (status.isDebug()) { 341 logger.debug("Rolling back JDBC transaction on Connection [" + con + "]"); 342 } 343 try { 344 con.rollback(); 345 } 346 catch (SQLException ex) { 347 throw new TransactionSystemException("Could not roll back JDBC transaction", ex); 348 } 349 } 350 351 @Override 352 protected void doSetRollbackOnly(DefaultTransactionStatus status) { 353 DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction(); 354 if (status.isDebug()) { 355 logger.debug("Setting JDBC transaction [" + txObject.getConnectionHolder().getConnection() + 356 "] rollback-only"); 357 } 358 txObject.setRollbackOnly(); 359 } 360 361 @Override 362 protected void doCleanupAfterCompletion(Object transaction) { 363 DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; 364 365 // Remove the connection holder from the thread, if exposed. 366 if (txObject.isNewConnectionHolder()) { 367 TransactionSynchronizationManager.unbindResource(obtainDataSource()); 368 } 369 370 // Reset connection. 371 Connection con = txObject.getConnectionHolder().getConnection(); 372 try { 373 if (txObject.isMustRestoreAutoCommit()) { 374 con.setAutoCommit(true); 375 } 376 DataSourceUtils.resetConnectionAfterTransaction( 377 con, txObject.getPreviousIsolationLevel(), txObject.isReadOnly()); 378 } 379 catch (Throwable ex) { 380 logger.debug("Could not reset JDBC Connection after transaction", ex); 381 } 382 383 if (txObject.isNewConnectionHolder()) { 384 if (logger.isDebugEnabled()) { 385 logger.debug("Releasing JDBC Connection [" + con + "] after transaction"); 386 } 387 DataSourceUtils.releaseConnection(con, this.dataSource); 388 } 389 390 txObject.getConnectionHolder().clear(); 391 } 392 393 394 /** 395 * Prepare the transactional {@code Connection} right after transaction begin. 396 * <p>The default implementation executes a "SET TRANSACTION READ ONLY" statement 397 * if the {@link #setEnforceReadOnly "enforceReadOnly"} flag is set to {@code true} 398 * and the transaction definition indicates a read-only transaction. 399 * <p>The "SET TRANSACTION READ ONLY" is understood by Oracle, MySQL and Postgres 400 * and may work with other databases as well. If you'd like to adapt this treatment, 401 * override this method accordingly. 402 * @param con the transactional JDBC Connection 403 * @param definition the current transaction definition 404 * @throws SQLException if thrown by JDBC API 405 * @since 4.3.7 406 * @see #setEnforceReadOnly 407 */ 408 protected void prepareTransactionalConnection(Connection con, TransactionDefinition definition) 409 throws SQLException { 410 411 if (isEnforceReadOnly() && definition.isReadOnly()) { 412 try (Statement stmt = con.createStatement()) { 413 stmt.executeUpdate("SET TRANSACTION READ ONLY"); 414 } 415 } 416 } 417 418 419 /** 420 * DataSource transaction object, representing a ConnectionHolder. 421 * Used as transaction object by DataSourceTransactionManager. 422 */ 423 private static class DataSourceTransactionObject extends JdbcTransactionObjectSupport { 424 425 private boolean newConnectionHolder; 426 427 private boolean mustRestoreAutoCommit; 428 429 public void setConnectionHolder(@Nullable ConnectionHolder connectionHolder, boolean newConnectionHolder) { 430 super.setConnectionHolder(connectionHolder); 431 this.newConnectionHolder = newConnectionHolder; 432 } 433 434 public boolean isNewConnectionHolder() { 435 return this.newConnectionHolder; 436 } 437 438 public void setMustRestoreAutoCommit(boolean mustRestoreAutoCommit) { 439 this.mustRestoreAutoCommit = mustRestoreAutoCommit; 440 } 441 442 public boolean isMustRestoreAutoCommit() { 443 return this.mustRestoreAutoCommit; 444 } 445 446 public void setRollbackOnly() { 447 getConnectionHolder().setRollbackOnly(); 448 } 449 450 @Override 451 public boolean isRollbackOnly() { 452 return getConnectionHolder().isRollbackOnly(); 453 } 454 455 @Override 456 public void flush() { 457 if (TransactionSynchronizationManager.isSynchronizationActive()) { 458 TransactionSynchronizationUtils.triggerFlush(); 459 } 460 } 461 } 462 463}