KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > orm > toplink > TopLinkTransactionManager


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.orm.toplink;
18
19 import java.sql.Connection JavaDoc;
20 import java.sql.SQLException JavaDoc;
21
22 import javax.sql.DataSource JavaDoc;
23
24 import oracle.toplink.exceptions.DatabaseException;
25 import oracle.toplink.exceptions.TopLinkException;
26 import oracle.toplink.internal.databaseaccess.Accessor;
27 import oracle.toplink.internal.databaseaccess.DatabaseAccessor;
28 import oracle.toplink.sessions.Session;
29
30 import org.springframework.beans.factory.InitializingBean;
31 import org.springframework.dao.DataAccessException;
32 import org.springframework.jdbc.datasource.ConnectionHolder;
33 import org.springframework.jdbc.datasource.JdbcTransactionObjectSupport;
34 import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
35 import org.springframework.jdbc.support.SQLExceptionTranslator;
36 import org.springframework.transaction.CannotCreateTransactionException;
37 import org.springframework.transaction.TransactionDefinition;
38 import org.springframework.transaction.support.AbstractPlatformTransactionManager;
39 import org.springframework.transaction.support.DefaultTransactionStatus;
40 import org.springframework.transaction.support.ResourceTransactionManager;
41 import org.springframework.transaction.support.TransactionSynchronizationManager;
42
43 /**
44  * {@link org.springframework.transaction.PlatformTransactionManager} implementation
45  * for a single TopLink {@link SessionFactory}. Binds a TopLink Session from the
46  * specified factory to the thread, potentially allowing for one thread-bound Session
47  * per factory. {@link SessionFactoryUtils} and {@link TopLinkTemplate} are aware
48  * of thread-bound Sessions and participate in such transactions automatically.
49  * Using either of those or going through <code>Session.getActiveUnitOfWork()</code> is
50  * required for TopLink access code supporting this transaction handling mechanism.
51  *
52  * <p>This transaction manager is appropriate for applications that use a single
53  * TopLink SessionFactory for transactional data access. JTA (usually through
54  * {@link org.springframework.transaction.jta.JtaTransactionManager}) is necessary
55  * for accessing multiple transactional resources within the same transaction.
56  * Note that you need to configure TopLink with an appropriate external transaction
57  * controller in order to make it participate in JTA transactions.
58  *
59  * <p>This transaction manager also supports direct DataSource access within a transaction
60  * (i.e. plain JDBC code working with the same DataSource), but only for transactions
61  * that are <i>not</i> marked as read-only. This allows for mixing services which
62  * access TopLink and services which use plain JDBC (without being aware of TopLink)!
63  * Application code needs to stick to the same simple Connection lookup pattern as
64  * with {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}
65  * (i.e. {@link org.springframework.jdbc.datasource.DataSourceUtils#getConnection}
66  * or going through a
67  * {@link org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy}).
68  *
69  * <p>Note: To be able to register a DataSource's Connection for plain JDBC code,
70  * this instance needs to be aware of the DataSource ({@link #setDataSource}).
71  * The given DataSource should obviously match the one used by the given TopLink
72  * SessionFactory.
73  *
74  * <p>On JDBC 3.0, this transaction manager supports nested transactions via JDBC 3.0
75  * Savepoints. The {@link #setNestedTransactionAllowed} "nestedTransactionAllowed"}
76  * flag defaults to "false", though, as nested transactions will just apply to the
77  * JDBC Connection, not to the TopLink PersistenceManager and its cached objects.
78  * You can manually set the flag to "true" if you want to use nested transactions
79  * for JDBC access code which participates in TopLink transactions (provided that
80  * your JDBC driver supports Savepoints). <i>Note that TopLink itself does not
81  * support nested transactions! Hence, do not expect TopLink access code to
82  * semantically participate in a nested transaction.</i>
83  *
84  * <p>Thanks to Slavik Markovich for implementing the initial TopLink support prototype!
85  *
86  * @author Juergen Hoeller
87  * @author <a HREF="mailto:james.x.clark@oracle.com">James Clark</a>
88  * @since 1.2
89  * @see #setSessionFactory
90  * @see #setDataSource
91  * @see LocalSessionFactoryBean
92  * @see SessionFactoryUtils#getSession
93  * @see SessionFactoryUtils#releaseSession
94  * @see TopLinkTemplate
95  * @see oracle.toplink.sessions.Session#getActiveUnitOfWork()
96  * @see org.springframework.jdbc.datasource.DataSourceUtils#getConnection
97  * @see org.springframework.jdbc.datasource.DataSourceUtils#applyTransactionTimeout
98  * @see org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection
99  * @see org.springframework.jdbc.core.JdbcTemplate
100  * @see org.springframework.jdbc.datasource.DataSourceTransactionManager
101   * @see org.springframework.transaction.jta.JtaTransactionManager
102  */

103 public class TopLinkTransactionManager extends AbstractPlatformTransactionManager
104         implements ResourceTransactionManager, InitializingBean {
105
106     private SessionFactory sessionFactory;
107
108     private DataSource JavaDoc dataSource;
109
110     private boolean lazyDatabaseTransaction = false;
111
112     private SQLExceptionTranslator jdbcExceptionTranslator;
113
114
115     /**
116      * Create a new TopLinkTransactionManager instance.
117      * A SessionFactory has to be specified to be able to use it.
118      * @see #setSessionFactory
119      */

120     public TopLinkTransactionManager() {
121     }
122
123     /**
124      * Create a new TopLinkTransactionManager instance.
125      * @param sessionFactory the TopLink SessionFactory to manage transactions for
126      */

127     public TopLinkTransactionManager(SessionFactory sessionFactory) {
128         this.sessionFactory = sessionFactory;
129         afterPropertiesSet();
130     }
131
132     /**
133      * Set the the TopLink SessionFactory to manage transactions for.
134      * This will usually be a ServerSessionFactory.
135      * <p>The passed-in SessionFactory will be asked for a plain Session
136      * in case of a read-only transaction (where no active UnitOfWork is
137      * supposed to be available), and for a managed Session else (with an
138      * active UnitOfWork that will be committed by this transaction manager).
139      * @see ServerSessionFactory
140      * @see SessionFactory#createSession()
141      * @see SessionFactory#createManagedClientSession()
142      */

143     public void setSessionFactory(SessionFactory sessionFactory) {
144         this.sessionFactory = sessionFactory;
145     }
146
147     /**
148      * Return the SessionFactory that this instance should manage transactions for.
149      */

150     public SessionFactory getSessionFactory() {
151         return this.sessionFactory;
152     }
153
154     /**
155      * Set the JDBC DataSource that this instance should manage transactions for.
156    * The DataSource should match the one used by the TopLink SessionFactory:
157      * for example, you could specify the same JNDI DataSource for both.
158      * <p>A transactional JDBC Connection for this DataSource will be provided to
159      * application code accessing this DataSource directly via DataSourceUtils
160      * or JdbcTemplate. The Connection will be taken from the TopLink Session.
161      * <b>This will only happen for transactions that are <i>not</i> marked
162      * as read-only.</b> TopLink does not support database transactions for pure
163      * read-only operations on a Session (that is, without a UnitOfWork).
164      * <p>Note that you need to use a TopLink Session with a DatabaseAccessor
165      * to allow for exposing TopLink transactions as JDBC transactions. This is
166      * the case of all standard TopLink configurations.
167      * <p>The DataSource specified here should be the target DataSource to manage
168      * transactions for, not a TransactionAwareDataSourceProxy. Only data access
169      * code may work with TransactionAwareDataSourceProxy, while the transaction
170      * manager needs to work on the underlying target DataSource. If there's
171      * nevertheless a TransactionAwareDataSourceProxy passed in, it will be
172      * unwrapped to extract its target DataSource.
173      * @see org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy
174      * @see org.springframework.jdbc.datasource.DataSourceUtils
175      * @see org.springframework.jdbc.core.JdbcTemplate
176      */

177     public void setDataSource(DataSource JavaDoc dataSource) {
178         if (dataSource instanceof TransactionAwareDataSourceProxy) {
179             // If we got a TransactionAwareDataSourceProxy, we need to perform transactions
180
// for its underlying target DataSource, else data access code won't see
181
// properly exposed transactions (i.e. transactions for the target DataSource).
182
this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
183         }
184         else {
185             this.dataSource = dataSource;
186         }
187     }
188
189     /**
190      * Return the JDBC DataSource that this instance manages transactions for.
191      */

192     public DataSource JavaDoc getDataSource() {
193         return this.dataSource;
194     }
195
196     /**
197      * Set whether to lazily start a database transaction within a TopLink
198      * transaction.
199      * <p>By default, database transactions are started early. This allows
200      * for reusing the same JDBC Connection throughout an entire transaction,
201      * including read operations, and also for exposing TopLink transactions
202      * to JDBC access code (working on the same DataSource).
203      * <p>It is only recommended to switch this flag to "true" when no JDBC access
204      * code is involved in any of the transactions, and when it is acceptable to
205      * perform read operations outside of the transactional JDBC Connection.
206      * @see #setDataSource(javax.sql.DataSource)
207      * @see oracle.toplink.sessions.UnitOfWork#beginEarlyTransaction()
208      */

209     public void setLazyDatabaseTransaction(boolean lazyDatabaseTransaction) {
210         this.lazyDatabaseTransaction = lazyDatabaseTransaction;
211     }
212
213     /**
214      * Return whether to lazily start a database transaction within a TopLink
215      * transaction.
216      */

217     public boolean isLazyDatabaseTransaction() {
218         return this.lazyDatabaseTransaction;
219     }
220
221     /**
222      * Set the JDBC exception translator for this transaction manager.
223      * <p>Applied to any SQLException root cause of a TopLink DatabaseException
224      * that is thrown on commit. The default is to rely on TopLink's native
225      * exception translation.
226      * @param jdbcExceptionTranslator the exception translator
227      * @see oracle.toplink.exceptions.DatabaseException
228      * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
229      * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator
230      * @see #setDataSource(javax.sql.DataSource)
231      */

232     public void setJdbcExceptionTranslator(SQLExceptionTranslator jdbcExceptionTranslator) {
233         this.jdbcExceptionTranslator = jdbcExceptionTranslator;
234     }
235
236     /**
237      * Return the JDBC exception translator for this transaction manager, if any.
238      */

239     public SQLExceptionTranslator getJdbcExceptionTranslator() {
240         return this.jdbcExceptionTranslator;
241     }
242
243     public void afterPropertiesSet() {
244         if (getSessionFactory() == null) {
245             throw new IllegalArgumentException JavaDoc("Property 'sessionFactory' is required");
246         }
247     }
248
249
250     public Object JavaDoc getResourceFactory() {
251         return getSessionFactory();
252     }
253
254     protected Object JavaDoc doGetTransaction() {
255         TopLinkTransactionObject txObject = new TopLinkTransactionObject();
256         SessionHolder sessionHolder = (SessionHolder)
257                 TransactionSynchronizationManager.getResource(this.sessionFactory);
258         txObject.setSessionHolder(sessionHolder);
259         return txObject;
260     }
261
262     protected boolean isExistingTransaction(Object JavaDoc transaction) {
263         TopLinkTransactionObject txObject = (TopLinkTransactionObject) transaction;
264         return (txObject.getSessionHolder() != null);
265     }
266
267     protected void doBegin(Object JavaDoc transaction, TransactionDefinition definition) {
268         Session session = null;
269
270         try {
271             if (!definition.isReadOnly()) {
272                 logger.debug("Creating managed TopLink Session with active UnitOfWork for read-write transaction");
273                 session = getSessionFactory().createManagedClientSession();
274             }
275             else {
276                 logger.debug("Creating plain TopLink Session without active UnitOfWork for read-only transaction");
277                 session = getSessionFactory().createSession();
278             }
279
280             if (logger.isDebugEnabled()) {
281                 logger.debug("Opened new session [" + session + "] for TopLink transaction");
282             }
283
284             TopLinkTransactionObject txObject = (TopLinkTransactionObject) transaction;
285             txObject.setSessionHolder(new SessionHolder(session));
286             txObject.getSessionHolder().setSynchronizedWithTransaction(true);
287
288             // Check isolation level.
289
switch (definition.getIsolationLevel()) {
290                 case TransactionDefinition.ISOLATION_READ_UNCOMMITTED:
291                     // TODO warn when queries are executed without the conformResultsInUnitOfWork setting
292
break;
293                 case TransactionDefinition.ISOLATION_REPEATABLE_READ:
294                     // TODO warn when queries are executed against a read-only Session
295
break;
296                 case TransactionDefinition.ISOLATION_SERIALIZABLE:
297                     // TODO warn if the TransactionIsolation settings on the DatabaseLogin are wrong
298
break;
299             }
300
301             // Register transaction timeout.
302
int timeout = determineTimeout(definition);
303             if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
304                 txObject.getSessionHolder().setTimeoutInSeconds(timeout);
305             }
306
307             // Enforce early database transaction for TopLink read-write transaction,
308
// unless we are explicitly told to use lazy transactions.
309
if (!definition.isReadOnly() && !isLazyDatabaseTransaction()) {
310                 session.getActiveUnitOfWork().beginEarlyTransaction();
311             }
312
313             // Register the TopLink Session's JDBC Connection for the DataSource, if set.
314
if (getDataSource() != null) {
315                 Connection JavaDoc con = getJdbcConnection(session);
316                 if (con != null) {
317                     ConnectionHolder conHolder = new ConnectionHolder(con);
318                     if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
319                         conHolder.setTimeoutInSeconds(timeout);
320                     }
321                     if (logger.isDebugEnabled()) {
322                         logger.debug("Exposing TopLink transaction as JDBC transaction [" + con + "]");
323                     }
324                     TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
325                     txObject.setConnectionHolder(conHolder);
326                 }
327                 else {
328                     if (logger.isDebugEnabled()) {
329                         logger.debug("Not exposing TopLink transaction [" + session +
330                                 "] as JDBC transaction because no JDBC Connection could be retrieved from it");
331                     }
332                 }
333             }
334
335             // Bind the session holder to the thread.
336
TransactionSynchronizationManager.bindResource(getSessionFactory(), txObject.getSessionHolder());
337         }
338
339         catch (Exception JavaDoc ex) {
340             SessionFactoryUtils.releaseSession(session, getSessionFactory());
341             throw new CannotCreateTransactionException("Could not open TopLink Session for transaction", ex);
342         }
343     }
344
345     /**
346      * Extract the underlying JDBC Connection from the given TopLink Session.
347      * <p>Default implementation casts to <code>oracle.toplink.publicinterface.Session</code>
348      * and fetches the Connection from the DatabaseAccessor exposed there.
349      * @param session the current TopLink Session
350      * @return the underlying JDBC Connection, or <code>null</code> if none found
351      * @see oracle.toplink.publicinterface.Session#getAccessor()
352      * @see oracle.toplink.internal.databaseaccess.DatabaseAccessor#getConnection()
353      */

354     protected Connection JavaDoc getJdbcConnection(Session session) {
355         if (!(session instanceof oracle.toplink.publicinterface.Session)) {
356             if (logger.isDebugEnabled()) {
357                 logger.debug("TopLink Session [" + session +
358                         "] does not derive from [oracle.toplink.publicinterface.Session]");
359             }
360             return null;
361         }
362         Accessor accessor = ((oracle.toplink.publicinterface.Session) session).getAccessor();
363         if (!(accessor instanceof DatabaseAccessor)) {
364             if (logger.isDebugEnabled()) {
365                 logger.debug("TopLink Accessor [" + accessor +
366                         "] does not derive from [oracle.toplink.internal.databaseaccess.DatabaseAccessor]");
367             }
368             return null;
369         }
370         return ((DatabaseAccessor) accessor).getConnection();
371     }
372
373     protected Object JavaDoc doSuspend(Object JavaDoc transaction) {
374         TopLinkTransactionObject txObject = (TopLinkTransactionObject) transaction;
375         txObject.setSessionHolder(null);
376         return TransactionSynchronizationManager.unbindResource(getSessionFactory());
377     }
378
379     protected void doResume(Object JavaDoc transaction, Object JavaDoc suspendedResources) {
380         SessionHolder sessionHolder = (SessionHolder) suspendedResources;
381         if (TransactionSynchronizationManager.hasResource(getSessionFactory())) {
382             // From non-transactional code running in active transaction synchronization
383
// -> can be safely removed, will be closed on transaction completion.
384
TransactionSynchronizationManager.unbindResource(getSessionFactory());
385         }
386         TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
387     }
388
389     protected void doCommit(DefaultTransactionStatus status) {
390         TopLinkTransactionObject txObject = (TopLinkTransactionObject) status.getTransaction();
391         if (status.isDebug()) {
392             logger.debug("Committing TopLink transaction on session [" +
393                     txObject.getSessionHolder().getSession() + "]");
394         }
395         try {
396             if (!status.isReadOnly()) {
397                 txObject.getSessionHolder().getSession().getActiveUnitOfWork().commit();
398             }
399             txObject.getSessionHolder().clear();
400         }
401         catch (TopLinkException ex) {
402             throw convertTopLinkAccessException(ex);
403         }
404     }
405
406     protected void doRollback(DefaultTransactionStatus status) {
407         TopLinkTransactionObject txObject = (TopLinkTransactionObject) status.getTransaction();
408         if (status.isDebug()) {
409             logger.debug("Not committing TopLink transaction on session [" +
410                     txObject.getSessionHolder().getSession() + "]");
411         }
412         txObject.getSessionHolder().clear();
413     }
414
415     protected void doSetRollbackOnly(DefaultTransactionStatus status) {
416         TopLinkTransactionObject txObject = (TopLinkTransactionObject) status.getTransaction();
417         if (status.isDebug()) {
418             logger.debug("Setting TopLink transaction on session [" +
419                     txObject.getSessionHolder().getSession() + "] rollback-only");
420         }
421         txObject.getSessionHolder().setRollbackOnly();
422     }
423
424     protected void doCleanupAfterCompletion(Object JavaDoc transaction) {
425         TopLinkTransactionObject txObject = (TopLinkTransactionObject) transaction;
426
427         // Remove the session holder from the thread.
428
TransactionSynchronizationManager.unbindResource(getSessionFactory());
429
430         // Remove the JDBC connection holder from the thread, if exposed.
431
if (txObject.hasConnectionHolder()) {
432             TransactionSynchronizationManager.unbindResource(getDataSource());
433         }
434
435         Session session = txObject.getSessionHolder().getSession();
436         if (logger.isDebugEnabled()) {
437             logger.debug("Releasing TopLink Session [" + session + "] after transaction");
438         }
439         try {
440             session.release();
441         }
442         catch (Throwable JavaDoc ex) {
443             // just log it, to keep a transaction-related exception
444
logger.debug("Could not release TopLink Session after transaction", ex);
445         }
446     }
447
448     /**
449      * Convert the given TopLinkException to an appropriate exception from the
450      * <code>org.springframework.dao</code> hierarchy.
451      * <p>Will automatically apply a specified SQLExceptionTranslator to a
452      * TopLink DatabaseException, else rely on TopLink's default translation.
453      * @param ex TopLinkException that occured
454      * @return a corresponding DataAccessException
455      * @see SessionFactoryUtils#convertTopLinkAccessException
456      * @see #setJdbcExceptionTranslator
457      */

458     protected DataAccessException convertTopLinkAccessException(TopLinkException ex) {
459         if (getJdbcExceptionTranslator() != null && ex instanceof DatabaseException) {
460             Throwable JavaDoc internalEx = ex.getInternalException();
461             // Should always be a SQLException inside a DatabaseException.
462
if (internalEx instanceof SQLException JavaDoc) {
463                 return getJdbcExceptionTranslator().translate(
464                         "TopLink commit: " + ex.getMessage(), null, (SQLException JavaDoc) internalEx);
465             }
466         }
467         return SessionFactoryUtils.convertTopLinkAccessException(ex);
468     }
469
470
471     /**
472      * TopLink transaction object, representing a SessionHolder.
473      * Used as transaction object by TopLinkTransactionManager.
474      *
475      * <p>Derives from JdbcTransactionObjectSupport in order to inherit the
476      * capability to manage JDBC 3.0 Savepoints for underlying JDBC Connections.
477      */

478     private static class TopLinkTransactionObject extends JdbcTransactionObjectSupport {
479
480         private SessionHolder sessionHolder;
481
482         public void setSessionHolder(SessionHolder sessionHolder) {
483             this.sessionHolder = sessionHolder;
484         }
485
486         public SessionHolder getSessionHolder() {
487             return this.sessionHolder;
488         }
489
490         public boolean isRollbackOnly() {
491             return getSessionHolder().isRollbackOnly();
492         }
493     }
494
495 }
496
Popular Tags