001: /*
002: * Copyright 2002-2007 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.ResourceTransactionManager;
031: import org.springframework.transaction.support.TransactionSynchronizationManager;
032:
033: /**
034: * {@link org.springframework.transaction.PlatformTransactionManager}
035: * implementation for a single JDBC {@link javax.sql.DataSource}. This class is
036: * capable of working in any environment with any JDBC driver, as long as the setup
037: * uses a JDBC 2.0 Standard Extensions / JDBC 3.0 <code>javax.sql.DataSource</code>
038: * as its Connection factory mechanism. Binds a JDBC Connection from the specified
039: * DataSource to the current thread, potentially allowing for one thread-bound
040: * 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: * J2EE-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: * J2EE-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>On JDBC 3.0, this transaction manager supports nested transactions via the
080: * JDBC 3.0 {@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. a Jakarta Commons
089: * DBCP connection pool). Switching between this local strategy and a JTA
090: * environment is just a matter of configuration!
091: *
092: * @author Juergen Hoeller
093: * @since 02.05.2003
094: * @see #setNestedTransactionAllowed
095: * @see java.sql.Savepoint
096: * @see DataSourceUtils#getConnection(javax.sql.DataSource)
097: * @see DataSourceUtils#applyTransactionTimeout
098: * @see DataSourceUtils#releaseConnection
099: * @see TransactionAwareDataSourceProxy
100: * @see LazyConnectionDataSourceProxy
101: * @see org.springframework.jdbc.core.JdbcTemplate
102: */
103: public class DataSourceTransactionManager extends
104: AbstractPlatformTransactionManager implements
105: ResourceTransactionManager, InitializingBean {
106:
107: private DataSource dataSource;
108:
109: /**
110: * Create a new DataSourceTransactionManager instance.
111: * A DataSource has to be set to be able to use it.
112: * @see #setDataSource
113: */
114: public DataSourceTransactionManager() {
115: setNestedTransactionAllowed(true);
116: }
117:
118: /**
119: * Create a new DataSourceTransactionManager instance.
120: * @param dataSource JDBC DataSource to manage transactions for
121: */
122: public DataSourceTransactionManager(DataSource dataSource) {
123: this ();
124: setDataSource(dataSource);
125: afterPropertiesSet();
126: }
127:
128: /**
129: * Set the JDBC DataSource that this instance should manage transactions for.
130: * <p>This will typically be a locally defined DataSource, for example a
131: * Jakarta Commons DBCP connection pool. Alternatively, you can also drive
132: * transactions for a non-XA J2EE DataSource fetched from JNDI. For an XA
133: * DataSource, use JtaTransactionManager.
134: * <p>The DataSource specified here should be the target DataSource to manage
135: * transactions for, not a TransactionAwareDataSourceProxy. Only data access
136: * code may work with TransactionAwareDataSourceProxy, while the transaction
137: * manager needs to work on the underlying target DataSource. If there's
138: * nevertheless a TransactionAwareDataSourceProxy passed in, it will be
139: * unwrapped to extract its target DataSource.
140: * <p><b>The DataSource passed in here needs to return independent Connections.</b>
141: * The Connections may come from a pool (the typical case), but the DataSource
142: * must not return thread-scoped / request-scoped Connections or the like.
143: * @see TransactionAwareDataSourceProxy
144: * @see org.springframework.transaction.jta.JtaTransactionManager
145: */
146: public void setDataSource(DataSource dataSource) {
147: if (dataSource instanceof TransactionAwareDataSourceProxy) {
148: // If we got a TransactionAwareDataSourceProxy, we need to perform transactions
149: // for its underlying target DataSource, else data access code won't see
150: // properly exposed transactions (i.e. transactions for the target DataSource).
151: this .dataSource = ((TransactionAwareDataSourceProxy) dataSource)
152: .getTargetDataSource();
153: } else {
154: this .dataSource = dataSource;
155: }
156: }
157:
158: /**
159: * Return the JDBC DataSource that this instance manages transactions for.
160: */
161: public DataSource getDataSource() {
162: return this .dataSource;
163: }
164:
165: public void afterPropertiesSet() {
166: if (getDataSource() == null) {
167: throw new IllegalArgumentException(
168: "Property 'dataSource' is required");
169: }
170: }
171:
172: public Object getResourceFactory() {
173: return getDataSource();
174: }
175:
176: protected Object doGetTransaction() {
177: DataSourceTransactionObject txObject = new DataSourceTransactionObject();
178: txObject.setSavepointAllowed(isNestedTransactionAllowed());
179: ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager
180: .getResource(this .dataSource);
181: txObject.setConnectionHolder(conHolder, false);
182: return txObject;
183: }
184:
185: protected boolean isExistingTransaction(Object transaction) {
186: DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
187: return (txObject.getConnectionHolder() != null && txObject
188: .getConnectionHolder().isTransactionActive());
189: }
190:
191: /**
192: * This implementation sets the isolation level but ignores the timeout.
193: */
194: protected void doBegin(Object transaction,
195: TransactionDefinition definition) {
196: DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
197: Connection con = null;
198:
199: try {
200: if (txObject.getConnectionHolder() == null
201: || txObject.getConnectionHolder()
202: .isSynchronizedWithTransaction()) {
203: Connection newCon = this .dataSource.getConnection();
204: if (logger.isDebugEnabled()) {
205: logger.debug("Acquired Connection [" + newCon
206: + "] for JDBC transaction");
207: }
208: txObject.setConnectionHolder(new ConnectionHolder(
209: newCon), true);
210: }
211:
212: txObject.getConnectionHolder()
213: .setSynchronizedWithTransaction(true);
214: con = txObject.getConnectionHolder().getConnection();
215:
216: Integer previousIsolationLevel = DataSourceUtils
217: .prepareConnectionForTransaction(con, definition);
218: txObject.setPreviousIsolationLevel(previousIsolationLevel);
219:
220: // Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
221: // so we don't want to do it unnecessarily (for example if we've explicitly
222: // configured the connection pool to set it already).
223: if (con.getAutoCommit()) {
224: txObject.setMustRestoreAutoCommit(true);
225: if (logger.isDebugEnabled()) {
226: logger.debug("Switching JDBC Connection [" + con
227: + "] to manual commit");
228: }
229: con.setAutoCommit(false);
230: }
231: txObject.getConnectionHolder().setTransactionActive(true);
232:
233: int timeout = determineTimeout(definition);
234: if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
235: txObject.getConnectionHolder().setTimeoutInSeconds(
236: timeout);
237: }
238:
239: // Bind the session holder to the thread.
240: if (txObject.isNewConnectionHolder()) {
241: TransactionSynchronizationManager
242: .bindResource(getDataSource(), txObject
243: .getConnectionHolder());
244: }
245: }
246:
247: catch (SQLException ex) {
248: DataSourceUtils.releaseConnection(con, this .dataSource);
249: throw new CannotCreateTransactionException(
250: "Could not open JDBC Connection for transaction",
251: ex);
252: }
253: }
254:
255: protected Object doSuspend(Object transaction) {
256: DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
257: txObject.setConnectionHolder(null);
258: ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager
259: .unbindResource(this .dataSource);
260: return conHolder;
261: }
262:
263: protected void doResume(Object transaction,
264: Object suspendedResources) {
265: ConnectionHolder conHolder = (ConnectionHolder) suspendedResources;
266: TransactionSynchronizationManager.bindResource(this .dataSource,
267: conHolder);
268: }
269:
270: protected void doCommit(DefaultTransactionStatus status) {
271: DataSourceTransactionObject txObject = (DataSourceTransactionObject) status
272: .getTransaction();
273: Connection con = txObject.getConnectionHolder().getConnection();
274: if (status.isDebug()) {
275: logger.debug("Committing JDBC transaction on Connection ["
276: + con + "]");
277: }
278: try {
279: con.commit();
280: } catch (SQLException ex) {
281: throw new TransactionSystemException(
282: "Could not commit JDBC transaction", ex);
283: }
284: }
285:
286: protected void doRollback(DefaultTransactionStatus status) {
287: DataSourceTransactionObject txObject = (DataSourceTransactionObject) status
288: .getTransaction();
289: Connection con = txObject.getConnectionHolder().getConnection();
290: if (status.isDebug()) {
291: logger
292: .debug("Rolling back JDBC transaction on Connection ["
293: + con + "]");
294: }
295: try {
296: con.rollback();
297: } catch (SQLException ex) {
298: throw new TransactionSystemException(
299: "Could not roll back JDBC transaction", ex);
300: }
301: }
302:
303: protected void doSetRollbackOnly(DefaultTransactionStatus status) {
304: DataSourceTransactionObject txObject = (DataSourceTransactionObject) status
305: .getTransaction();
306: if (status.isDebug()) {
307: logger.debug("Setting JDBC transaction ["
308: + txObject.getConnectionHolder().getConnection()
309: + "] rollback-only");
310: }
311: txObject.setRollbackOnly();
312: }
313:
314: protected void doCleanupAfterCompletion(Object transaction) {
315: DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
316:
317: // Remove the connection holder from the thread, if exposed.
318: if (txObject.isNewConnectionHolder()) {
319: TransactionSynchronizationManager
320: .unbindResource(this .dataSource);
321: }
322:
323: // Reset connection.
324: Connection con = txObject.getConnectionHolder().getConnection();
325: try {
326: if (txObject.isMustRestoreAutoCommit()) {
327: con.setAutoCommit(true);
328: }
329: DataSourceUtils.resetConnectionAfterTransaction(con,
330: txObject.getPreviousIsolationLevel());
331: } catch (Throwable ex) {
332: logger
333: .debug(
334: "Could not reset JDBC Connection after transaction",
335: ex);
336: }
337:
338: if (txObject.isNewConnectionHolder()) {
339: if (logger.isDebugEnabled()) {
340: logger.debug("Releasing JDBC Connection [" + con
341: + "] after transaction");
342: }
343: DataSourceUtils.releaseConnection(con, this .dataSource);
344: }
345:
346: txObject.getConnectionHolder().clear();
347: }
348:
349: /**
350: * DataSource transaction object, representing a ConnectionHolder.
351: * Used as transaction object by DataSourceTransactionManager.
352: */
353: private static class DataSourceTransactionObject extends
354: JdbcTransactionObjectSupport {
355:
356: private boolean newConnectionHolder;
357:
358: private boolean mustRestoreAutoCommit;
359:
360: public void setConnectionHolder(
361: ConnectionHolder connectionHolder,
362: boolean newConnectionHolder) {
363: super .setConnectionHolder(connectionHolder);
364: this .newConnectionHolder = newConnectionHolder;
365: }
366:
367: public boolean isNewConnectionHolder() {
368: return newConnectionHolder;
369: }
370:
371: public boolean hasTransaction() {
372: return (getConnectionHolder() != null && getConnectionHolder()
373: .isTransactionActive());
374: }
375:
376: public void setMustRestoreAutoCommit(
377: boolean mustRestoreAutoCommit) {
378: this .mustRestoreAutoCommit = mustRestoreAutoCommit;
379: }
380:
381: public boolean isMustRestoreAutoCommit() {
382: return mustRestoreAutoCommit;
383: }
384:
385: public void setRollbackOnly() {
386: getConnectionHolder().setRollbackOnly();
387: }
388:
389: public boolean isRollbackOnly() {
390: return getConnectionHolder().isRollbackOnly();
391: }
392: }
393:
394: }
|