All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.orm.toplink.TopLinkTransactionManager Maven / Gradle / Ivy

There is a newer version: 5.3.34
Show newest version
/*
 * Copyright 2002-2007 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.orm.toplink;

import java.sql.Connection;
import java.sql.SQLException;

import javax.sql.DataSource;

import oracle.toplink.exceptions.DatabaseException;
import oracle.toplink.exceptions.TopLinkException;
import oracle.toplink.internal.databaseaccess.Accessor;
import oracle.toplink.internal.databaseaccess.DatabaseAccessor;
import oracle.toplink.sessions.Session;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.datasource.ConnectionHolder;
import org.springframework.jdbc.datasource.JdbcTransactionObjectSupport;
import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
import org.springframework.jdbc.support.SQLExceptionTranslator;
import org.springframework.transaction.CannotCreateTransactionException;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
import org.springframework.transaction.support.DefaultTransactionStatus;
import org.springframework.transaction.support.ResourceTransactionManager;
import org.springframework.transaction.support.TransactionSynchronizationManager;

/**
 * {@link org.springframework.transaction.PlatformTransactionManager} implementation
 * for a single TopLink {@link SessionFactory}. Binds a TopLink Session from the
 * specified factory to the thread, potentially allowing for one thread-bound Session
 * per factory. {@link SessionFactoryUtils} and {@link TopLinkTemplate} are aware
 * of thread-bound Sessions and participate in such transactions automatically.
 * Using either of those or going through Session.getActiveUnitOfWork() is
 * required for TopLink access code supporting this transaction handling mechanism.
 *
 * 

This transaction manager is appropriate for applications that use a single * TopLink SessionFactory for transactional data access. JTA (usually through * {@link org.springframework.transaction.jta.JtaTransactionManager}) is necessary * for accessing multiple transactional resources within the same transaction. * Note that you need to configure TopLink with an appropriate external transaction * controller in order to make it participate in JTA transactions. * *

This transaction manager also supports direct DataSource access within a transaction * (i.e. plain JDBC code working with the same DataSource), but only for transactions * that are not marked as read-only. This allows for mixing services which * access TopLink and services which use plain JDBC (without being aware of TopLink)! * Application code needs to stick to the same simple Connection lookup pattern as * with {@link org.springframework.jdbc.datasource.DataSourceTransactionManager} * (i.e. {@link org.springframework.jdbc.datasource.DataSourceUtils#getConnection} * or going through a * {@link org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy}). * *

Note: To be able to register a DataSource's Connection for plain JDBC code, * this instance needs to be aware of the DataSource ({@link #setDataSource}). * The given DataSource should obviously match the one used by the given TopLink * SessionFactory. * *

On JDBC 3.0, this transaction manager supports nested transactions via JDBC 3.0 * Savepoints. The {@link #setNestedTransactionAllowed} "nestedTransactionAllowed"} * flag defaults to "false", though, as nested transactions will just apply to the * JDBC Connection, not to the TopLink PersistenceManager and its cached objects. * You can manually set the flag to "true" if you want to use nested transactions * for JDBC access code which participates in TopLink transactions (provided that * your JDBC driver supports Savepoints). Note that TopLink itself does not * support nested transactions! Hence, do not expect TopLink access code to * semantically participate in a nested transaction. * *

Thanks to Slavik Markovich for implementing the initial TopLink support prototype! * * @author Juergen Hoeller * @author James Clark * @since 1.2 * @see #setSessionFactory * @see #setDataSource * @see LocalSessionFactoryBean * @see SessionFactoryUtils#getSession * @see SessionFactoryUtils#releaseSession * @see TopLinkTemplate * @see oracle.toplink.sessions.Session#getActiveUnitOfWork() * @see org.springframework.jdbc.datasource.DataSourceUtils#getConnection * @see org.springframework.jdbc.datasource.DataSourceUtils#applyTransactionTimeout * @see org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection * @see org.springframework.jdbc.core.JdbcTemplate * @see org.springframework.jdbc.datasource.DataSourceTransactionManager * @see org.springframework.transaction.jta.JtaTransactionManager */ public class TopLinkTransactionManager extends AbstractPlatformTransactionManager implements ResourceTransactionManager, InitializingBean { private SessionFactory sessionFactory; private DataSource dataSource; private boolean lazyDatabaseTransaction = false; private SQLExceptionTranslator jdbcExceptionTranslator; /** * Create a new TopLinkTransactionManager instance. * A SessionFactory has to be specified to be able to use it. * @see #setSessionFactory */ public TopLinkTransactionManager() { } /** * Create a new TopLinkTransactionManager instance. * @param sessionFactory the TopLink SessionFactory to manage transactions for */ public TopLinkTransactionManager(SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; afterPropertiesSet(); } /** * Set the the TopLink SessionFactory to manage transactions for. * This will usually be a ServerSessionFactory. *

The passed-in SessionFactory will be asked for a plain Session * in case of a read-only transaction (where no active UnitOfWork is * supposed to be available), and for a managed Session else (with an * active UnitOfWork that will be committed by this transaction manager). * @see ServerSessionFactory * @see SessionFactory#createSession() * @see SessionFactory#createManagedClientSession() */ public void setSessionFactory(SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; } /** * Return the SessionFactory that this instance should manage transactions for. */ public SessionFactory getSessionFactory() { return this.sessionFactory; } /** * Set the JDBC DataSource that this instance should manage transactions for. * The DataSource should match the one used by the TopLink SessionFactory: * for example, you could specify the same JNDI DataSource for both. *

A transactional JDBC Connection for this DataSource will be provided to * application code accessing this DataSource directly via DataSourceUtils * or JdbcTemplate. The Connection will be taken from the TopLink Session. * This will only happen for transactions that are not marked * as read-only. TopLink does not support database transactions for pure * read-only operations on a Session (that is, without a UnitOfWork). *

Note that you need to use a TopLink Session with a DatabaseAccessor * to allow for exposing TopLink transactions as JDBC transactions. This is * the case of all standard TopLink configurations. *

The DataSource specified here should be the target DataSource to manage * transactions for, not a TransactionAwareDataSourceProxy. Only data access * code may work with TransactionAwareDataSourceProxy, while the transaction * manager needs to work on the underlying target DataSource. If there's * nevertheless a TransactionAwareDataSourceProxy passed in, it will be * unwrapped to extract its target DataSource. * @see org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy * @see org.springframework.jdbc.datasource.DataSourceUtils * @see org.springframework.jdbc.core.JdbcTemplate */ public void setDataSource(DataSource dataSource) { if (dataSource instanceof TransactionAwareDataSourceProxy) { // If we got a TransactionAwareDataSourceProxy, we need to perform transactions // for its underlying target DataSource, else data access code won't see // properly exposed transactions (i.e. transactions for the target DataSource). this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource(); } else { this.dataSource = dataSource; } } /** * Return the JDBC DataSource that this instance manages transactions for. */ public DataSource getDataSource() { return this.dataSource; } /** * Set whether to lazily start a database transaction within a TopLink * transaction. *

By default, database transactions are started early. This allows * for reusing the same JDBC Connection throughout an entire transaction, * including read operations, and also for exposing TopLink transactions * to JDBC access code (working on the same DataSource). *

It is only recommended to switch this flag to "true" when no JDBC access * code is involved in any of the transactions, and when it is acceptable to * perform read operations outside of the transactional JDBC Connection. * @see #setDataSource(javax.sql.DataSource) * @see oracle.toplink.sessions.UnitOfWork#beginEarlyTransaction() */ public void setLazyDatabaseTransaction(boolean lazyDatabaseTransaction) { this.lazyDatabaseTransaction = lazyDatabaseTransaction; } /** * Return whether to lazily start a database transaction within a TopLink * transaction. */ public boolean isLazyDatabaseTransaction() { return this.lazyDatabaseTransaction; } /** * Set the JDBC exception translator for this transaction manager. *

Applied to any SQLException root cause of a TopLink DatabaseException * that is thrown on commit. The default is to rely on TopLink's native * exception translation. * @param jdbcExceptionTranslator the exception translator * @see oracle.toplink.exceptions.DatabaseException * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator * @see #setDataSource(javax.sql.DataSource) */ public void setJdbcExceptionTranslator(SQLExceptionTranslator jdbcExceptionTranslator) { this.jdbcExceptionTranslator = jdbcExceptionTranslator; } /** * Return the JDBC exception translator for this transaction manager, if any. */ public SQLExceptionTranslator getJdbcExceptionTranslator() { return this.jdbcExceptionTranslator; } public void afterPropertiesSet() { if (getSessionFactory() == null) { throw new IllegalArgumentException("Property 'sessionFactory' is required"); } } public Object getResourceFactory() { return getSessionFactory(); } protected Object doGetTransaction() { TopLinkTransactionObject txObject = new TopLinkTransactionObject(); SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(this.sessionFactory); txObject.setSessionHolder(sessionHolder); return txObject; } protected boolean isExistingTransaction(Object transaction) { TopLinkTransactionObject txObject = (TopLinkTransactionObject) transaction; return (txObject.getSessionHolder() != null); } protected void doBegin(Object transaction, TransactionDefinition definition) { Session session = null; try { if (!definition.isReadOnly()) { logger.debug("Creating managed TopLink Session with active UnitOfWork for read-write transaction"); session = getSessionFactory().createManagedClientSession(); } else { logger.debug("Creating plain TopLink Session without active UnitOfWork for read-only transaction"); session = getSessionFactory().createSession(); } if (logger.isDebugEnabled()) { logger.debug("Opened new session [" + session + "] for TopLink transaction"); } TopLinkTransactionObject txObject = (TopLinkTransactionObject) transaction; txObject.setSessionHolder(new SessionHolder(session)); txObject.getSessionHolder().setSynchronizedWithTransaction(true); // Check isolation level. switch (definition.getIsolationLevel()) { case TransactionDefinition.ISOLATION_READ_UNCOMMITTED: // TODO warn when queries are executed without the conformResultsInUnitOfWork setting break; case TransactionDefinition.ISOLATION_REPEATABLE_READ: // TODO warn when queries are executed against a read-only Session break; case TransactionDefinition.ISOLATION_SERIALIZABLE: // TODO warn if the TransactionIsolation settings on the DatabaseLogin are wrong break; } // Register transaction timeout. int timeout = determineTimeout(definition); if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { txObject.getSessionHolder().setTimeoutInSeconds(timeout); } // Enforce early database transaction for TopLink read-write transaction, // unless we are explicitly told to use lazy transactions. if (!definition.isReadOnly() && !isLazyDatabaseTransaction()) { session.getActiveUnitOfWork().beginEarlyTransaction(); } // Register the TopLink Session's JDBC Connection for the DataSource, if set. if (getDataSource() != null) { Connection con = getJdbcConnection(session); if (con != null) { ConnectionHolder conHolder = new ConnectionHolder(con); if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { conHolder.setTimeoutInSeconds(timeout); } if (logger.isDebugEnabled()) { logger.debug("Exposing TopLink transaction as JDBC transaction [" + con + "]"); } TransactionSynchronizationManager.bindResource(getDataSource(), conHolder); txObject.setConnectionHolder(conHolder); } else { if (logger.isDebugEnabled()) { logger.debug("Not exposing TopLink transaction [" + session + "] as JDBC transaction because no JDBC Connection could be retrieved from it"); } } } // Bind the session holder to the thread. TransactionSynchronizationManager.bindResource(getSessionFactory(), txObject.getSessionHolder()); } catch (Exception ex) { SessionFactoryUtils.releaseSession(session, getSessionFactory()); throw new CannotCreateTransactionException("Could not open TopLink Session for transaction", ex); } } /** * Extract the underlying JDBC Connection from the given TopLink Session. *

Default implementation casts to oracle.toplink.publicinterface.Session * and fetches the Connection from the DatabaseAccessor exposed there. * @param session the current TopLink Session * @return the underlying JDBC Connection, or null if none found * @see oracle.toplink.publicinterface.Session#getAccessor() * @see oracle.toplink.internal.databaseaccess.DatabaseAccessor#getConnection() */ protected Connection getJdbcConnection(Session session) { if (!(session instanceof oracle.toplink.publicinterface.Session)) { if (logger.isDebugEnabled()) { logger.debug("TopLink Session [" + session + "] does not derive from [oracle.toplink.publicinterface.Session]"); } return null; } Accessor accessor = ((oracle.toplink.publicinterface.Session) session).getAccessor(); if (!(accessor instanceof DatabaseAccessor)) { if (logger.isDebugEnabled()) { logger.debug("TopLink Accessor [" + accessor + "] does not derive from [oracle.toplink.internal.databaseaccess.DatabaseAccessor]"); } return null; } return ((DatabaseAccessor) accessor).getConnection(); } protected Object doSuspend(Object transaction) { TopLinkTransactionObject txObject = (TopLinkTransactionObject) transaction; txObject.setSessionHolder(null); return TransactionSynchronizationManager.unbindResource(getSessionFactory()); } protected void doResume(Object transaction, Object suspendedResources) { SessionHolder sessionHolder = (SessionHolder) suspendedResources; if (TransactionSynchronizationManager.hasResource(getSessionFactory())) { // From non-transactional code running in active transaction synchronization // -> can be safely removed, will be closed on transaction completion. TransactionSynchronizationManager.unbindResource(getSessionFactory()); } TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder); } protected void doCommit(DefaultTransactionStatus status) { TopLinkTransactionObject txObject = (TopLinkTransactionObject) status.getTransaction(); if (status.isDebug()) { logger.debug("Committing TopLink transaction on session [" + txObject.getSessionHolder().getSession() + "]"); } try { if (!status.isReadOnly()) { txObject.getSessionHolder().getSession().getActiveUnitOfWork().commit(); } txObject.getSessionHolder().clear(); } catch (TopLinkException ex) { throw convertTopLinkAccessException(ex); } } protected void doRollback(DefaultTransactionStatus status) { TopLinkTransactionObject txObject = (TopLinkTransactionObject) status.getTransaction(); if (status.isDebug()) { logger.debug("Not committing TopLink transaction on session [" + txObject.getSessionHolder().getSession() + "]"); } txObject.getSessionHolder().clear(); } protected void doSetRollbackOnly(DefaultTransactionStatus status) { TopLinkTransactionObject txObject = (TopLinkTransactionObject) status.getTransaction(); if (status.isDebug()) { logger.debug("Setting TopLink transaction on session [" + txObject.getSessionHolder().getSession() + "] rollback-only"); } txObject.getSessionHolder().setRollbackOnly(); } protected void doCleanupAfterCompletion(Object transaction) { TopLinkTransactionObject txObject = (TopLinkTransactionObject) transaction; // Remove the session holder from the thread. TransactionSynchronizationManager.unbindResource(getSessionFactory()); // Remove the JDBC connection holder from the thread, if exposed. if (txObject.hasConnectionHolder()) { TransactionSynchronizationManager.unbindResource(getDataSource()); } Session session = txObject.getSessionHolder().getSession(); if (logger.isDebugEnabled()) { logger.debug("Releasing TopLink Session [" + session + "] after transaction"); } try { session.release(); } catch (Throwable ex) { // just log it, to keep a transaction-related exception logger.debug("Could not release TopLink Session after transaction", ex); } } /** * Convert the given TopLinkException to an appropriate exception from the * org.springframework.dao hierarchy. *

Will automatically apply a specified SQLExceptionTranslator to a * TopLink DatabaseException, else rely on TopLink's default translation. * @param ex TopLinkException that occured * @return a corresponding DataAccessException * @see SessionFactoryUtils#convertTopLinkAccessException * @see #setJdbcExceptionTranslator */ protected DataAccessException convertTopLinkAccessException(TopLinkException ex) { if (getJdbcExceptionTranslator() != null && ex instanceof DatabaseException) { Throwable internalEx = ex.getInternalException(); // Should always be a SQLException inside a DatabaseException. if (internalEx instanceof SQLException) { return getJdbcExceptionTranslator().translate( "TopLink commit: " + ex.getMessage(), null, (SQLException) internalEx); } } return SessionFactoryUtils.convertTopLinkAccessException(ex); } /** * TopLink transaction object, representing a SessionHolder. * Used as transaction object by TopLinkTransactionManager. * *

Derives from JdbcTransactionObjectSupport in order to inherit the * capability to manage JDBC 3.0 Savepoints for underlying JDBC Connections. */ private static class TopLinkTransactionObject extends JdbcTransactionObjectSupport { private SessionHolder sessionHolder; public void setSessionHolder(SessionHolder sessionHolder) { this.sessionHolder = sessionHolder; } public SessionHolder getSessionHolder() { return this.sessionHolder; } public boolean isRollbackOnly() { return getSessionHolder().isRollbackOnly(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy