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