KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > jdbc > datasource > DataSourceTransactionManager


1 /*
2  * Copyright 2002-2007 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.springframework.jdbc.datasource;
18
19 import java.sql.Connection JavaDoc;
20 import java.sql.SQLException JavaDoc;
21
22 import javax.sql.DataSource JavaDoc;
23
24 import org.springframework.beans.factory.InitializingBean;
25 import org.springframework.transaction.CannotCreateTransactionException;
26 import org.springframework.transaction.TransactionDefinition;
27 import org.springframework.transaction.TransactionSystemException;
28 import org.springframework.transaction.support.AbstractPlatformTransactionManager;
29 import org.springframework.transaction.support.DefaultTransactionStatus;
30 import org.springframework.transaction.support.TransactionSynchronizationManager;
31 import org.springframework.transaction.support.ResourceTransactionManager;
32
33 /**
34  * {@link org.springframework.transaction.PlatformTransactionManager}
35  * implementation for a single JDBC {@link javax.sql.DataSource}. This class is
36  * capable of working in any environment with any JDBC driver, as long as the setup
37  * uses a JDBC 2.0 Standard Extensions / JDBC 3.0 <code>javax.sql.DataSource</code>
38  * as its Connection factory mechanism. Binds a JDBC Connection from the specified
39  * DataSource to the current thread, potentially allowing for one thread-bound
40  * Connection per DataSource.
41  *
42  * <p>Application code is required to retrieve the JDBC Connection via
43  * {@link DataSourceUtils#getConnection(DataSource)} instead of a standard
44  * J2EE-style {@link DataSource#getConnection()} call. Spring classes such as
45  * {@link org.springframework.jdbc.core.JdbcTemplate} use this strategy implicitly.
46  * If not used in combination with this transaction manager, the
47  * {@link DataSourceUtils} lookup strategy behaves exactly like the native
48  * DataSource lookup; it can thus be used in a portable fashion.
49  *
50  * <p>Alternatively, you can allow application code to work with the standard
51  * J2EE-style lookup pattern {@link DataSource#getConnection()}, for example for
52  * legacy code that is not aware of Spring at all. In that case, define a
53  * {@link TransactionAwareDataSourceProxy} for your target DataSource, and pass
54  * that proxy DataSource to your DAOs, which will automatically participate in
55  * Spring-managed transactions when accessing it.
56  *
57  * <p>Supports custom isolation levels, and timeouts which get applied as
58  * appropriate JDBC statement timeouts. To support the latter, application code
59  * must either use {@link org.springframework.jdbc.core.JdbcTemplate}, call
60  * {@link DataSourceUtils#applyTransactionTimeout} for each created JDBC Statement,
61  * or go through a {@link TransactionAwareDataSourceProxy} which will create
62  * timeout-aware JDBC Connections and Statements automatically.
63  *
64  * <p>Consider defining a {@link LazyConnectionDataSourceProxy} for your target
65  * DataSource, pointing both this transaction manager and your DAOs to it.
66  * This will lead to optimized handling of "empty" transactions, i.e. of transactions
67  * without any JDBC statements executed. A LazyConnectionDataSourceProxy will not fetch
68  * an actual JDBC Connection from the target DataSource until a Statement gets executed,
69  * lazily applying the specified transaction settings to the target Connection.
70  *
71  * <p>On JDBC 3.0, this transaction manager supports nested transactions via the
72  * JDBC 3.0 {@link java.sql.Savepoint} mechanism. The
73  * {@link #setNestedTransactionAllowed "nestedTransactionAllowed"} flag defaults
74  * to "true", since nested transactions will work without restrictions on JDBC
75  * drivers that support savepoints (such as the Oracle JDBC driver).
76  *
77  * <p>This transaction manager can be used as a replacement for the
78  * {@link org.springframework.transaction.jta.JtaTransactionManager} in the single
79  * resource case, as it does not require a container that supports JTA, typically
80  * in combination with a locally defined JDBC DataSource (e.g. a Jakarta Commons
81  * DBCP connection pool). Switching between this local strategy and a JTA
82  * environment is just a matter of configuration!
83  *
84  * @author Juergen Hoeller
85  * @since 02.05.2003
86  * @see #setNestedTransactionAllowed
87  * @see java.sql.Savepoint
88  * @see DataSourceUtils#getConnection(javax.sql.DataSource)
89  * @see DataSourceUtils#applyTransactionTimeout
90  * @see DataSourceUtils#releaseConnection
91  * @see TransactionAwareDataSourceProxy
92  * @see LazyConnectionDataSourceProxy
93  * @see org.springframework.jdbc.core.JdbcTemplate
94  */

95 public class DataSourceTransactionManager extends AbstractPlatformTransactionManager
96         implements ResourceTransactionManager, InitializingBean {
97
98     private DataSource JavaDoc dataSource;
99
100
101     /**
102      * Create a new DataSourceTransactionManager instance.
103      * A DataSource has to be set to be able to use it.
104      * @see #setDataSource
105      */

106     public DataSourceTransactionManager() {
107         setNestedTransactionAllowed(true);
108     }
109
110     /**
111      * Create a new DataSourceTransactionManager instance.
112      * @param dataSource JDBC DataSource to manage transactions for
113      */

114     public DataSourceTransactionManager(DataSource JavaDoc dataSource) {
115         this();
116         setDataSource(dataSource);
117         afterPropertiesSet();
118     }
119
120     /**
121      * Set the JDBC DataSource that this instance should manage transactions for.
122      * <p>This will typically be a locally defined DataSource, for example a
123      * Jakarta Commons DBCP connection pool. Alternatively, you can also drive
124      * transactions for a non-XA J2EE DataSource fetched from JNDI. For an XA
125      * DataSource, use JtaTransactionManager.
126      * <p>The DataSource specified here should be the target DataSource to manage
127      * transactions for, not a TransactionAwareDataSourceProxy. Only data access
128      * code may work with TransactionAwareDataSourceProxy, while the transaction
129      * manager needs to work on the underlying target DataSource. If there's
130      * nevertheless a TransactionAwareDataSourceProxy passed in, it will be
131      * unwrapped to extract its target DataSource.
132      * @see TransactionAwareDataSourceProxy
133      * @see org.springframework.transaction.jta.JtaTransactionManager
134      */

135     public void setDataSource(DataSource JavaDoc dataSource) {
136         if (dataSource instanceof TransactionAwareDataSourceProxy) {
137             // If we got a TransactionAwareDataSourceProxy, we need to perform transactions
138
// for its underlying target DataSource, else data access code won't see
139
// properly exposed transactions (i.e. transactions for the target DataSource).
140
this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
141         }
142         else {
143             this.dataSource = dataSource;
144         }
145     }
146
147     /**
148      * Return the JDBC DataSource that this instance manages transactions for.
149      */

150     public DataSource JavaDoc getDataSource() {
151         return this.dataSource;
152     }
153
154     public void afterPropertiesSet() {
155         if (getDataSource() == null) {
156             throw new IllegalArgumentException JavaDoc("Property 'dataSource' is required");
157         }
158     }
159
160
161     public Object JavaDoc getResourceFactory() {
162         return getDataSource();
163     }
164
165     protected Object JavaDoc doGetTransaction() {
166         DataSourceTransactionObject txObject = new DataSourceTransactionObject();
167         txObject.setSavepointAllowed(isNestedTransactionAllowed());
168         ConnectionHolder conHolder =
169             (ConnectionHolder) TransactionSynchronizationManager.getResource(this.dataSource);
170         txObject.setConnectionHolder(conHolder, false);
171         return txObject;
172     }
173
174     protected boolean isExistingTransaction(Object JavaDoc transaction) {
175         DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
176         return (txObject.getConnectionHolder() != null && txObject.getConnectionHolder().isTransactionActive());
177     }
178
179     /**
180      * This implementation sets the isolation level but ignores the timeout.
181      */

182     protected void doBegin(Object JavaDoc transaction, TransactionDefinition definition) {
183         DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
184         Connection JavaDoc con = null;
185
186         try {
187             if (txObject.getConnectionHolder() == null ||
188                     txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
189                 Connection JavaDoc newCon = this.dataSource.getConnection();
190                 if (logger.isDebugEnabled()) {
191                     logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
192                 }
193                 txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
194             }
195
196             txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
197             con = txObject.getConnectionHolder().getConnection();
198
199             Integer JavaDoc previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
200             txObject.setPreviousIsolationLevel(previousIsolationLevel);
201
202             // Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
203
// so we don't want to do it unnecessarily (for example if we've explicitly
204
// configured the connection pool to set it already).
205
if (con.getAutoCommit()) {
206                 txObject.setMustRestoreAutoCommit(true);
207                 if (logger.isDebugEnabled()) {
208                     logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
209                 }
210                 con.setAutoCommit(false);
211             }
212             txObject.getConnectionHolder().setTransactionActive(true);
213
214             int timeout = determineTimeout(definition);
215             if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
216                 txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
217             }
218
219             // Bind the session holder to the thread.
220
if (txObject.isNewConnectionHolder()) {
221                 TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
222             }
223         }
224
225         catch (SQLException JavaDoc ex) {
226             DataSourceUtils.releaseConnection(con, this.dataSource);
227             throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
228         }
229     }
230
231     protected Object JavaDoc doSuspend(Object JavaDoc transaction) {
232         DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
233         txObject.setConnectionHolder(null);
234         ConnectionHolder conHolder = (ConnectionHolder)
235                 TransactionSynchronizationManager.unbindResource(this.dataSource);
236         return conHolder;
237     }
238
239     protected void doResume(Object JavaDoc transaction, Object JavaDoc suspendedResources) {
240         ConnectionHolder conHolder = (ConnectionHolder) suspendedResources;
241         TransactionSynchronizationManager.bindResource(this.dataSource, conHolder);
242     }
243
244     protected void doCommit(DefaultTransactionStatus status) {
245         DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
246         Connection JavaDoc con = txObject.getConnectionHolder().getConnection();
247         if (status.isDebug()) {
248             logger.debug("Committing JDBC transaction on Connection [" + con + "]");
249         }
250         try {
251             con.commit();
252         }
253         catch (SQLException JavaDoc ex) {
254             throw new TransactionSystemException("Could not commit JDBC transaction", ex);
255         }
256     }
257
258     protected void doRollback(DefaultTransactionStatus status) {
259         DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
260         Connection JavaDoc con = txObject.getConnectionHolder().getConnection();
261         if (status.isDebug()) {
262             logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
263         }
264         try {
265             con.rollback();
266         }
267         catch (SQLException JavaDoc ex) {
268             throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
269         }
270     }
271
272     protected void doSetRollbackOnly(DefaultTransactionStatus status) {
273         DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
274         if (status.isDebug()) {
275             logger.debug("Setting JDBC transaction [" + txObject.getConnectionHolder().getConnection() +
276                     "] rollback-only");
277         }
278         txObject.setRollbackOnly();
279     }
280
281     protected void doCleanupAfterCompletion(Object JavaDoc transaction) {
282         DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
283
284         // Remove the connection holder from the thread, if exposed.
285
if (txObject.isNewConnectionHolder()) {
286             TransactionSynchronizationManager.unbindResource(this.dataSource);
287         }
288
289         // Reset connection.
290
Connection JavaDoc con = txObject.getConnectionHolder().getConnection();
291         try {
292             if (txObject.isMustRestoreAutoCommit()) {
293                 con.setAutoCommit(true);
294             }
295             DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());
296         }
297         catch (Throwable JavaDoc ex) {
298             logger.debug("Could not reset JDBC Connection after transaction", ex);
299         }
300
301         if (txObject.isNewConnectionHolder()) {
302             if (logger.isDebugEnabled()) {
303                 logger.debug("Releasing JDBC Connection [" + con + "] after transaction");
304             }
305             DataSourceUtils.releaseConnection(con, this.dataSource);
306         }
307
308         txObject.getConnectionHolder().clear();
309     }
310
311
312     /**
313      * DataSource transaction object, representing a ConnectionHolder.
314      * Used as transaction object by DataSourceTransactionManager.
315      *
316      * <p>Derives from JdbcTransactionObjectSupport to inherit the capability
317      * to manage JDBC 3.0 Savepoints.
318      *
319      * @see ConnectionHolder
320      */

321     private static class DataSourceTransactionObject extends JdbcTransactionObjectSupport {
322
323         private boolean newConnectionHolder;
324
325         private boolean mustRestoreAutoCommit;
326
327         public void setConnectionHolder(ConnectionHolder connectionHolder, boolean newConnectionHolder) {
328             super.setConnectionHolder(connectionHolder);
329             this.newConnectionHolder = newConnectionHolder;
330         }
331
332         public boolean isNewConnectionHolder() {
333             return newConnectionHolder;
334         }
335
336         public boolean hasTransaction() {
337             return (getConnectionHolder() != null && getConnectionHolder().isTransactionActive());
338         }
339
340         public void setMustRestoreAutoCommit(boolean mustRestoreAutoCommit) {
341             this.mustRestoreAutoCommit = mustRestoreAutoCommit;
342         }
343
344         public boolean isMustRestoreAutoCommit() {
345             return mustRestoreAutoCommit;
346         }
347
348         public void setRollbackOnly() {
349             getConnectionHolder().setRollbackOnly();
350         }
351
352         public boolean isRollbackOnly() {
353             return getConnectionHolder().isRollbackOnly();
354         }
355     }
356
357 }
358
Popular Tags