001 /* 002 * Copyright 2002-2004 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 * http://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 017 package org.springframework.jdbc.datasource; 018 019 import java.sql.Connection; 020 import java.sql.SQLException; 021 022 import javax.sql.DataSource; 023 024 import org.springframework.beans.factory.InitializingBean; 025 import org.springframework.transaction.CannotCreateTransactionException; 026 import org.springframework.transaction.TransactionDefinition; 027 import org.springframework.transaction.TransactionSystemException; 028 import org.springframework.transaction.support.AbstractPlatformTransactionManager; 029 import org.springframework.transaction.support.DefaultTransactionStatus; 030 import org.springframework.transaction.support.TransactionSynchronizationManager; 031 032 /** 033 * PlatformTransactionManager implementation for a single JDBC DataSource. 034 * Binds a JDBC connection from the specified DataSource to the thread, 035 * potentially allowing for one thread connection per data source. 036 * 037 * <p>Application code is required to retrieve the JDBC Connection via 038 * <code>DataSourceUtils.getConnection(DataSource)</code> instead of J2EE's standard 039 * <code>DataSource.getConnection()</code>. This is recommended anyway, as it throws 040 * unchecked org.springframework.dao exceptions instead of checked SQLException. 041 * All framework classes like JdbcTemplate use this strategy implicitly. 042 * If not used with this transaction manager, the lookup strategy behaves exactly 043 * like the common one - it can thus be used in any case. 044 * 045 * <p>Alternatively, you can also allow application code to work with the standard 046 * J2EE lookup pattern <code>DataSource.getConnection()</code>, for example for 047 * legacy code that is not aware of Spring at all. In that case, define a 048 * TransactionAwareDataSourceProxy for your target DataSource, and pass that proxy 049 * DataSource to your DAOs, which will automatically participate in Spring-managed 050 * transactions through it. Note that DataSourceTransactionManager still needs to 051 * be wired with the target DataSource, driving transactions for it. 052 * 053 * <p>Supports custom isolation levels, and timeouts that get applied as 054 * appropriate JDBC statement query timeouts. To support the latter, 055 * application code must either use JdbcTemplate or call 056 * <code>DataSourceUtils.applyTransactionTimeout</code> for each created statement. 057 * 058 * <p>On JDBC 3.0, this transaction manager supports nested transactions via JDBC 059 * 3.0 Savepoints. The "nestedTransactionAllowed" flag defaults to true, as nested 060 * transactions work without restrictions on JDBC drivers that support Savepoints 061 * (like Oracle). 062 * 063 * <p>This implementation can be used instead of JtaTransactionManager in the single 064 * resource case, as it does not require the container to support JTA: typically, 065 * in combination with a locally defined JDBC DataSource like a Jakarta Commons DBCP 066 * connection pool. Switching between this local strategy and a JTA environment is 067 * just a matter of configuration, if you stick to the required connection lookup 068 * pattern. Note that JTA does not support custom isolation levels! 069 * 070 * @author Juergen Hoeller 071 * @since 02.05.2003 072 * @see #setNestedTransactionAllowed 073 * @see java.sql.Savepoint 074 * @see DataSourceUtils#getConnection 075 * @see DataSourceUtils#applyTransactionTimeout 076 * @see DataSourceUtils#closeConnectionIfNecessary 077 * @see TransactionAwareDataSourceProxy 078 * @see org.springframework.jdbc.core.JdbcTemplate 079 */ 080 public class DataSourceTransactionManager extends AbstractPlatformTransactionManager implements InitializingBean { 081 082 private DataSource dataSource; 083 084 085 /** 086 * Create a new DataSourceTransactionManager instance. 087 * A DataSource has to be set to be able to use it. 088 * @see #setDataSource 089 */ 090 public DataSourceTransactionManager() { 091 setNestedTransactionAllowed(true); 092 } 093 094 /** 095 * Create a new DataSourceTransactionManager instance. 096 * @param dataSource JDBC DataSource to manage transactions for 097 */ 098 public DataSourceTransactionManager(DataSource dataSource) { 099 this(); 100 this.dataSource = dataSource; 101 afterPropertiesSet(); 102 } 103 104 /** 105 * Set the JDBC DataSource that this instance should manage transactions for. 106 * <p>This will typically be a locally defined DataSource, for example a 107 * Jakarta Commons DBCP connection pool. Alternatively, you can also drive 108 * transactions for a non-XA J2EE DataSource fetched from JNDI. For an XA 109 * DataSource, use JtaTransactionManager. 110 * @see org.springframework.transaction.jta.JtaTransactionManager 111 */ 112 public void setDataSource(DataSource dataSource) { 113 this.dataSource = dataSource; 114 } 115 116 /** 117 * Return the JDBC DataSource that this instance manages transactions for. 118 */ 119 public DataSource getDataSource() { 120 return dataSource; 121 } 122 123 public void afterPropertiesSet() { 124 if (this.dataSource == null) { 125 throw new IllegalArgumentException("dataSource is required"); 126 } 127 } 128 129 130 protected Object doGetTransaction() { 131 DataSourceTransactionObject txObject = new DataSourceTransactionObject(); 132 txObject.setSavepointAllowed(isNestedTransactionAllowed()); 133 ConnectionHolder conHolder = 134 (ConnectionHolder) TransactionSynchronizationManager.getResource(this.dataSource); 135 txObject.setConnectionHolder(conHolder); 136 return txObject; 137 } 138 139 protected boolean isExistingTransaction(Object transaction) { 140 DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; 141 // Consider a pre-bound connection as transaction. 142 return (txObject.getConnectionHolder() != null); 143 } 144 145 /** 146 * This implementation sets the isolation level but ignores the timeout. 147 */ 148 protected void doBegin(Object transaction, TransactionDefinition definition) { 149 DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; 150 151 Connection con = DataSourceUtils.getConnection(this.dataSource, false); 152 if (logger.isDebugEnabled()) { 153 logger.debug("Opened connection [" + con + "] for JDBC transaction"); 154 } 155 156 txObject.setConnectionHolder(new ConnectionHolder(con)); 157 txObject.getConnectionHolder().setSynchronizedWithTransaction(true); 158 159 try { 160 Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition); 161 txObject.setPreviousIsolationLevel(previousIsolationLevel); 162 163 // Switch to manual commit if necessary. This is very expensive in some JDBC drivers, 164 // so we don't want to do it unnecessarily (for example if we're configured 165 // Commons DBCP to set it already). 166 if (con.getAutoCommit()) { 167 txObject.setMustRestoreAutoCommit(true); 168 if (logger.isDebugEnabled()) { 169 logger.debug("Switching JDBC connection [" + con + "] to manual commit"); 170 } 171 con.setAutoCommit(false); 172 } 173 174 if (definition.getTimeout() != TransactionDefinition.TIMEOUT_DEFAULT) { 175 txObject.getConnectionHolder().setTimeoutInSeconds(definition.getTimeout()); 176 } 177 TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder()); 178 } 179 180 catch (SQLException ex) { 181 DataSourceUtils.closeConnectionIfNecessary(con, this.dataSource); 182 throw new CannotCreateTransactionException("Could not configure JDBC connection for transaction", ex); 183 } 184 } 185 186 protected Object doSuspend(Object transaction) { 187 DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; 188 txObject.setConnectionHolder(null); 189 return TransactionSynchronizationManager.unbindResource(this.dataSource); 190 } 191 192 protected void doResume(Object transaction, Object suspendedResources) { 193 ConnectionHolder conHolder = (ConnectionHolder) suspendedResources; 194 TransactionSynchronizationManager.bindResource(this.dataSource, conHolder); 195 } 196 197 protected void doCommit(DefaultTransactionStatus status) { 198 DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction(); 199 Connection con = txObject.getConnectionHolder().getConnection(); 200 if (status.isDebug()) { 201 logger.debug("Committing JDBC transaction on connection [" + con + "]"); 202 } 203 try { Rate204 con.commit(); 205 } 206 catch (SQLException ex) { 207 throw new TransactionSystemException("Could not commit JDBC transaction", ex); 208 } 209 } 210 211 protected void doRollback(DefaultTransactionStatus status) { 212 DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction(); 213 Connection con = txObject.getConnectionHolder().getConnection(); 214 if (status.isDebug()) { 215 logger.debug("Rolling back JDBC transaction on connection [" + con + "]"); 216 } 217 try { 218 con.rollback(); 219 } 220 catch (SQLException ex) { 221 throw new TransactionSystemException("Could not roll back JDBC transaction", ex); 222 } 223 } 224 225 protected void doSetRollbackOnly(DefaultTransactionStatus status) { 226 DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction(); 227 if (status.isDebug()) { 228 logger.debug("Setting JDBC transaction [" + txObject.getConnectionHolder().getConnection() + 229 "] rollback-only"); 230 } 231 txObject.setRollbackOnly(); 232 } 233 234 protected void doCleanupAfterCompletion(Object transaction) { 235 DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; 236 237 // Remove the connection holder from the thread. 238 TransactionSynchronizationManager.unbindResource(this.dataSource); 239 txObject.getConnectionHolder().clear(); 240 241 // Reset connection. 242 Connection con = txObject.getConnectionHolder().getConnection(); 243 try { 244 if (txObject.isMustRestoreAutoCommit()) { 245 con.setAutoCommit(true); 246 } 247 DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel()); 248 } 249 catch (SQLException ex) { 250 logger.info("Could not reset JDBC connection after transaction", ex); 251 } 252 253 if (logger.isDebugEnabled()) { 254 logger.debug("Closing JDBC connection [" + con + "] after transaction"); 255 } 256 DataSourceUtils.closeConnectionIfNecessary(con, this.dataSource); 257 } 258 259 260 /** 261 * DataSource transaction object, representing a ConnectionHolder. 262 * Used as transaction object by DataSourceTransactionManager. 263 * 264 * <p>Derives from JdbcTransactionObjectSupport to inherit the capability 265 * to manage JDBC 3.0 Savepoints. 266 * 267 * @see ConnectionHolder 268 */ 269 private static class DataSourceTransactionObject extends JdbcTransactionObjectSupport { 270 271 private boolean mustRestoreAutoCommit; 272 273 public void setMustRestoreAutoCommit(boolean mustRestoreAutoCommit) { 274 this.mustRestoreAutoCommit = mustRestoreAutoCommit; 275 } 276 277 public boolean isMustRestoreAutoCommit() { 278 return mustRestoreAutoCommit; 279 } 280 281 public void setRollbackOnly() { 282 getConnectionHolder().setRollbackOnly(); 283 } 284 285 public boolean isRollbackOnly() { 286 return getConnectionHolder().isRollbackOnly(); 287 } 288 289 } 290 }