org.springframework.transaction.jta.JtaTransactionManager Maven / Gradle / Ivy
/*
* Copyright 2002-2005 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.transaction.jta;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.List;
import java.util.Properties;
import javax.naming.NamingException;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.InvalidTransactionException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jndi.JndiTemplate;
import org.springframework.transaction.CannotCreateTransactionException;
import org.springframework.transaction.HeuristicCompletionException;
import org.springframework.transaction.IllegalTransactionStateException;
import org.springframework.transaction.InvalidIsolationLevelException;
import org.springframework.transaction.NestedTransactionNotSupportedException;
import org.springframework.transaction.NoTransactionException;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionSuspensionNotSupportedException;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.transaction.UnexpectedRollbackException;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
import org.springframework.transaction.support.DefaultTransactionStatus;
import org.springframework.transaction.support.TransactionSynchronization;
/**
* PlatformTransactionManager implementation for JTA, i.e. J2EE container transactions.
* Can also work with a locally configured JTA implementation.
*
* This transaction manager is appropriate for handling distributed transactions,
* i.e. transactions that span multiple resources, and for managing transactions
* on a J2EE Connector (e.g. a persistence toolkit registered as JCA Connector).
* For a single JDBC DataSource, DataSourceTransactionManager is perfectly sufficient,
* and for accessing a single resource with Hibernate (including transactional cache),
* HibernateTransactionManager is appropriate.
*
*
Transaction synchronization is active by default, to allow data access support
* classes to register resources that are opened within the transaction for closing at
* transaction completion time. Spring's support classes for JDBC, Hibernate and JDO
* all perform such registration, allowing for reuse of the same Hibernate Session etc
* within the transaction. Standard JTA does not even guarantee that for Connections
* from a transactional JDBC DataSource: Spring's synchronization solves those issues.
*
*
Synchronization is also leveraged for transactional cache handling with Hibernate.
* Therefore, as long as JtaTransactionManager drives the JTA transactions, there is
* no need to configure Hibernate's JTATransaction strategy or a container-specific
* Hibernate TransactionManagerLookup. However, certain JTA implementations are
* restrictive in terms of what JDBC calls they allow after transaction completion,
* complaining even on close calls: In that case, it is indeed necessary to configure a
* Hibernate TransactionManagerLookup, potentially via Spring's LocalSessionFactoryBean.
*
*
If JtaTransactionManager participates in an existing JTA transaction, e.g. from
* EJB CMT, synchronization will be triggered on finishing the nested transaction,
* before passing transaction control back to the J2EE container. In this case, a
* container-specific Hibernate TransactionManagerLookup is the only way to achieve
* exact afterCompletion callbacks for transactional cache handling with Hibernate.
*
*
For typical JTA transactions (REQUIRED, SUPPORTS, MANDATORY, NEVER), a plain
* JtaTransactionManager definition is all you need, completely portable across all
* J2EE servers. This corresponds to the functionality of the JTA UserTransaction,
* for which J2EE specifies a standard JNDI name ("java:comp/UserTransaction").
* There is no need to configure a server-specific TransactionManager lookup for this
* kind of JTA usage.
*
*
Note: Advanced JTA usage below. Dealing with these mechanisms is not
* necessary for typical usage scenarios.
*
*
Transaction suspension (REQUIRES_NEW, NOT_SUPPORTED) is just available with
* a JTA TransactionManager being registered, via the "transactionManagerName" or
* "transactionManager" property. The location of this well-defined JTA object is
* not specified by J2EE; it is specific to each J2EE server, often kept
* in JNDI like the JTA UserTransaction. Some well-known JNDI locations are:
*
* - "java:comp/UserTransaction" for Resin 2.x, Oracle OC4J (Orion),
* JOnAS (JOTM), BEA WebLogic
*
- "java:comp/TransactionManager" for Resin 3.x
*
- "java:/TransactionManager" for JBoss
*
*
* All of these cases are autodetected by JtaTransactionManager (since Spring 1.2),
* provided that the "autodetectTransactionManager" flag is set to "true" (which it is
* by default). Consequently, JtaTransactionManager will support transaction suspension
* out-of-the-box on many J2EE servers.
*
*
A JNDI lookup can also be factored out into a corresponding JndiObjectFactoryBean,
* passed into JtaTransactionManager's "transactionManager" property. Such a bean
* definition can then be reused by other objects, for example Spring's
* LocalSessionFactoryBean for Hibernate (see below).
*
*
For IBM WebSphere and standalone JOTM, static accessor methods are required to
* obtain the JTA TransactionManager: Therefore, WebSphere and JOTM have their own
* FactoryBean implementations, to be wired with the "transactionManager" property.
* In case of JotmFactoryBean, the same JTA object implements UserTransaction too:
* Therefore, passing the object to the "userTransaction" property is sufficient.
*
*
It is also possible to specify a JTA TransactionManager only, either through
* the corresponding constructor or through the "transactionManager" property.
* In the latter case, the "userTransactionName" property needs to be set to null
,
* to suppress the default "java:comp/UserTransaction" JNDI lookup and thus enforce
* use of the given JTA TransactionManager even for begin, commit and rollback.
*
*
Note: Support for the JTA TransactionManager interface is not required by J2EE.
* Almost all J2EE servers expose it, but do so as extension to J2EE. There might be some
* issues with compatibility, despite the TransactionManager interface being part of JTA.
* The only currently known problem is resuming a transaction on WebLogic, which by default
* fails if the suspended transaction was marked rollback-only; for other usages, it works
* properly. Use Spring's WebLogicJtaTransactionManager to enforce a resume in any case.
*
*
The JTA TransactionManager can also be used to register custom synchronizations
* with the JTA transaction itself instead of Spring's transaction manager. This is
* particularly useful for closing resources with strict JTA implementations such as
* Weblogic's or WebSphere's that do not allow any access to resources after transaction
* completion, not even for cleanup. For example, Hibernate access is affected by this
* issue, as outlined above in the discussion of transaction synchronization.
*
*
Spring's LocalSessionFactoryBean for Hibernate supports plugging a given
* JTA TransactionManager into Hibernate's TransactionManagerLookup mechanism,
* for Hibernate-driven cache synchronization and proper cleanup without warnings.
* The same JTA TransactionManager configuration as above can be used in this case
* (with a JndiObjectFactoryBean for a JNDI lookup, or one of the FactoryBeans),
* avoiding double configuration. Alternatively, specify corresponding Hibernate
* properties (see Hibernate docs for details).
*
*
This standard JtaTransactionManager supports timeouts but not per-transaction
* isolation levels. Custom subclasses can override doJtaBegin
for
* specific JTA implementations to provide this functionality; Spring includes a
* corresponding WebLogicJtaTransactionManager class, for example. Such adapters
* for specific J2EE transaction coordinators can also expose transaction names
* for monitoring; with standard JTA, transaction names will be ignored.
*
*
Consider using WebLogicJtaTransactionManager on BEA WebLogic, which supports
* the full power of Spring's transaction definitions on WebLogic's transaction
* coordinator, beyond standard JTA: transaction names, per-transaction
* isolation levels, and proper resuming of transactions in all cases.
* WebLogicJtaTransactionManager automatically adapts to WebLogic 7.0 or 8.1+.
*
*
This class is serializable. Active synchronizations do not survive serialization,
* though.
*
* @author Juergen Hoeller
* @since 24.03.2003
* @see #setUserTransactionName
* @see #setUserTransaction
* @see #setTransactionManagerName
* @see #setTransactionManager
* @see #doJtaBegin
* @see JotmFactoryBean
* @see WebSphereTransactionManagerFactoryBean
* @see WebLogicJtaTransactionManager
* @see org.springframework.jndi.JndiObjectFactoryBean
* @see org.springframework.orm.hibernate.LocalSessionFactoryBean#setJtaTransactionManager
*/
public class JtaTransactionManager extends AbstractPlatformTransactionManager
implements InitializingBean, Serializable {
/**
* Default JNDI location for the JTA UserTransaction. Many J2EE servers
* also provide support for the JTA TransactionManager interface there.
* @see #setUserTransactionName
* @see #setAutodetectTransactionManager
*/
public static final String DEFAULT_USER_TRANSACTION_NAME = "java:comp/UserTransaction";
/**
* Fallback JNDI locations for the JTA TransactionManager. Applied if
* the JTA UserTransaction does not implement the JTA TransactionManager
* interface, provided that the "autodetectTransactionManager" flag is "true".
* @see #setTransactionManagerName
* @see #setAutodetectTransactionManager
*/
public static final String[] FALLBACK_TRANSACTION_MANAGER_NAMES =
new String[] {"java:comp/TransactionManager", "java:/TransactionManager"};
private transient JndiTemplate jndiTemplate = new JndiTemplate();
private transient UserTransaction userTransaction;
private String userTransactionName = DEFAULT_USER_TRANSACTION_NAME;
private boolean cacheUserTransaction = true;
private transient TransactionManager transactionManager;
private String transactionManagerName;
private boolean autodetectTransactionManager = true;
/**
* Create a new JtaTransactionManager instance, to be configured as bean.
* Invoke afterPropertiesSet to activate the configuration.
* @see #setUserTransactionName
* @see #setUserTransaction
* @see #setTransactionManagerName
* @see #setTransactionManager
* @see #afterPropertiesSet
*/
public JtaTransactionManager() {
setNestedTransactionAllowed(true);
}
/**
* Create a new JtaTransactionManager instance.
* @param userTransaction the JTA UserTransaction to use as direct reference
*/
public JtaTransactionManager(UserTransaction userTransaction) {
this();
this.userTransaction = userTransaction;
afterPropertiesSet();
}
/**
* Create a new JtaTransactionManager instance.
* @param userTransaction the JTA UserTransaction to use as direct reference
* @param transactionManager the JTA TransactionManager to use as direct reference
*/
public JtaTransactionManager(UserTransaction userTransaction, TransactionManager transactionManager) {
this();
this.userTransaction = userTransaction;
this.transactionManager = transactionManager;
afterPropertiesSet();
}
/**
* Create a new JtaTransactionManager instance.
* @param transactionManager the JTA TransactionManager to use as direct reference
*/
public JtaTransactionManager(TransactionManager transactionManager) {
this();
// Do not attempt UserTransaction lookup: use given TransactionManager
// to get a UserTransaction handle.
this.userTransactionName = null;
this.transactionManager = transactionManager;
afterPropertiesSet();
}
/**
* Set the JndiTemplate to use for JNDI lookups.
* A default one is used if not set.
*/
public void setJndiTemplate(JndiTemplate jndiTemplate) {
if (jndiTemplate == null) {
throw new IllegalArgumentException("jndiTemplate must not be null");
}
this.jndiTemplate = jndiTemplate;
}
/**
* Return the JndiTemplate used for JNDI lookups.
*/
public JndiTemplate getJndiTemplate() {
return jndiTemplate;
}
/**
* Set the JNDI environment to use for JNDI lookups.
* Creates a JndiTemplate with the given environment settings.
* @see #setJndiTemplate
*/
public void setJndiEnvironment(Properties jndiEnvironment) {
this.jndiTemplate = new JndiTemplate(jndiEnvironment);
}
/**
* Return the JNDI environment to use for JNDI lookups.
*/
public Properties getJndiEnvironment() {
return this.jndiTemplate.getEnvironment();
}
/**
* Set the JTA UserTransaction to use as direct reference.
* Typically just used for local JTA setups; in a J2EE environment,
* the UserTransaction will always be fetched from JNDI.
* @see #setUserTransactionName
*/
public void setUserTransaction(UserTransaction userTransaction) {
this.userTransaction = userTransaction;
}
/**
* Return the JTA UserTransaction that this transaction manager uses.
*/
public UserTransaction getUserTransaction() {
return userTransaction;
}
/**
* Set the JNDI name of the JTA UserTransaction.
* The J2EE default "java:comp/UserTransaction" is used if not set.
* @see #DEFAULT_USER_TRANSACTION_NAME
* @see #setUserTransaction
*/
public void setUserTransactionName(String userTransactionName) {
this.userTransactionName = userTransactionName;
}
/**
* Set whether to cache the JTA UserTransaction object fetched from JNDI.
*
Default is "true": UserTransaction lookup will only happen at startup,
* reusing the same UserTransaction handle for all transactions of all threads.
* This is the most efficient choice for all application servers that provide
* a shared UserTransaction object (the typical case).
*
Turn this flag off to enforce a fresh lookup of the UserTransaction
* for every transaction. This is only necessary for application servers
* that return a new UserTransaction for every transaction, keeping state
* tied to the UserTransaction object itself rather than the current thread.
*/
public void setCacheUserTransaction(boolean cacheUserTransaction) {
this.cacheUserTransaction = cacheUserTransaction;
}
/**
* Set the JTA TransactionManager to use as direct reference.
*
A TransactionManager is necessary for suspending and resuming transactions,
* as this not supported by the UserTransaction interface.
*
Note that the TransactionManager will be autodetected if the JTA
* UserTransaction object implements the JTA TransactionManager interface too.
* @see #setTransactionManagerName
* @see #setAutodetectTransactionManager
*/
public void setTransactionManager(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
/**
* Return the JTA TransactionManager that this transaction manager uses.
*/
public TransactionManager getTransactionManager() {
return transactionManager;
}
/**
* Set the JNDI name of the JTA TransactionManager.
*
A TransactionManager is necessary for suspending and resuming transactions,
* as this not supported by the UserTransaction interface.
*
Note that the TransactionManager will be autodetected if the JTA
* UserTransaction object implements the JTA TransactionManager interface too.
* @see #setTransactionManager
*/
public void setTransactionManagerName(String transactionManagerName) {
this.transactionManagerName = transactionManagerName;
}
/**
* Set whether to autodetect a JTA UserTransaction object that implements
* the JTA TransactionManager interface too (i.e. the JNDI location for the
* TransactionManager is "java:comp/UserTransaction", same as for the UserTransaction).
* Also checks the fallback JNDI locations "java:comp/TransactionManager" and
* "java:/TransactionManager". Will proceed without TransactionManager if none found.
*
Default is "true". Can be turned off to deliberately ignore an available
* TransactionManager, for example when there are known issues with suspend/resume
* and any attempt to use REQUIRES_NEW or NOT_SUPPORTED should fail fast.
* @see #FALLBACK_TRANSACTION_MANAGER_NAMES
*/
public void setAutodetectTransactionManager(boolean autodetectTransactionManager) {
this.autodetectTransactionManager = autodetectTransactionManager;
}
public void afterPropertiesSet() throws TransactionSystemException {
// Fetch JTA UserTransaction from JNDI, if necessary.
if (this.userTransaction == null) {
if (this.userTransactionName != null) {
this.userTransaction = lookupUserTransaction(this.userTransactionName);
}
else {
this.userTransaction = retrieveUserTransaction();
}
}
// Fetch JTA TransactionManager from JNDI, if necessary.
if (this.transactionManager == null) {
if (this.transactionManagerName != null) {
this.transactionManager = lookupTransactionManager(this.transactionManagerName);
}
else {
this.transactionManager = retrieveTransactionManager();
}
}
// Autodetect UserTransaction object that implements TransactionManager,
// and check fallback JNDI locations else.
if (this.transactionManager == null && this.autodetectTransactionManager) {
this.transactionManager = findTransactionManager(this.userTransaction);
}
// If only JTA TransactionManager specified, create UserTransaction handle for it.
if (this.userTransaction == null && this.transactionManager != null) {
if (this.transactionManager instanceof UserTransaction) {
this.userTransaction = (UserTransaction) this.transactionManager;
}
else {
this.userTransaction = new UserTransactionAdapter(this.transactionManager);
}
}
// We at least need the JTA UserTransaction.
if (this.userTransaction != null) {
if (logger.isInfoEnabled()) {
logger.info("Using JTA UserTransaction: " + this.userTransaction);
}
}
else {
throw new IllegalArgumentException(
"Either 'userTransaction' or 'userTransactionName' or 'transactionManager' " +
"or 'transactionManagerName' must be set");
}
// For transaction suspension, the JTA TransactionManager is necessary too.
if (this.transactionManager != null) {
if (logger.isInfoEnabled()) {
logger.info("Using JTA TransactionManager: " + this.transactionManager);
}
}
else {
logger.warn("No JTA TransactionManager found: " +
"transaction suspension and synchronization with existing JTA transactions not available");
}
}
/**
* Look up the JTA UserTransaction in JNDI via the configured name.
* Called by afterPropertiesSet
if no direct UserTransaction reference was set.
* Can be overridden in subclasses to provide a different UserTransaction object.
* @param userTransactionName the JNDI name of the UserTransaction
* @return the UserTransaction object
* @throws TransactionSystemException if the JNDI lookup failed
* @see #setJndiTemplate
* @see #setUserTransactionName
*/
protected UserTransaction lookupUserTransaction(String userTransactionName)
throws TransactionSystemException {
try {
if (logger.isDebugEnabled()) {
logger.debug("Retrieving JTA UserTransaction from JNDI location [" + userTransactionName + "]");
}
UserTransaction ut = (UserTransaction)
getJndiTemplate().lookup(userTransactionName, UserTransaction.class);
return ut;
}
catch (NamingException ex) {
throw new TransactionSystemException(
"JTA UserTransaction is not available at JNDI location [" + userTransactionName + "]", ex);
}
}
/**
* Look up the JTA TransactionManager in JNDI via the configured name.
* Called by afterPropertiesSet
if no direct TransactionManager reference was set.
* Can be overridden in subclasses to provide a different TransactionManager object.
* @param transactionManagerName the JNDI name of the TransactionManager
* @return the UserTransaction object
* @throws TransactionSystemException if the JNDI lookup failed
* @see #setJndiTemplate
* @see #setTransactionManagerName
*/
protected TransactionManager lookupTransactionManager(String transactionManagerName)
throws TransactionSystemException {
try {
if (logger.isDebugEnabled()) {
logger.debug("Retrieving JTA TransactionManager from JNDI location [" + transactionManagerName + "]");
}
TransactionManager tm = (TransactionManager)
getJndiTemplate().lookup(transactionManagerName, TransactionManager.class);
return tm;
}
catch (NamingException ex) {
throw new TransactionSystemException(
"JTA TransactionManager is not available at JNDI location [" + transactionManagerName + "]", ex);
}
}
/**
* Allows subclasses to retrieve the JTA UserTransaction in a vendor-specific manner.
* Only called if no "userTransaction" or "userTransactionName" specified.
*
Default implementation simply returns null.
* @return the JTA UserTransaction handle to use, or null if none found
* @throws TransactionSystemException in case of errors
* @see #setUserTransaction
* @see #setUserTransactionName
*/
protected UserTransaction retrieveUserTransaction() throws TransactionSystemException {
return null;
}
/**
* Allows subclasses to retrieve the JTA TransactionManager in a vendor-specific manner.
* Only called if no "transactionManager" or "transactionManagerName" specified.
*
Default implementation simply returns null.
* @return the JTA TransactionManager handle to use, or null if none found
* @throws TransactionSystemException in case of errors
* @see #setTransactionManager
* @see #setTransactionManagerName
*/
protected TransactionManager retrieveTransactionManager() throws TransactionSystemException {
return null;
}
/**
* Find the JTA TransactionManager through autodetection:
* checking whether the UserTransaction object implements the
* TransactionManager, and checking the fallback JNDI locations.
* @param ut the JTA UserTransaction object
* @return the JTA TransactionManager reference, or null if not found
* @see #FALLBACK_TRANSACTION_MANAGER_NAMES
*/
protected TransactionManager findTransactionManager(UserTransaction ut) {
if (ut instanceof TransactionManager) {
if (logger.isDebugEnabled()) {
logger.debug("JTA UserTransaction object [" + ut + "] implements TransactionManager");
}
return (TransactionManager) ut;
}
// Check fallback JNDI locations.
for (int i = 0; i < FALLBACK_TRANSACTION_MANAGER_NAMES.length; i++) {
String jndiName = FALLBACK_TRANSACTION_MANAGER_NAMES[i];
try {
TransactionManager tm = lookupTransactionManager(jndiName);
logger.debug("JTA TransactionManager found at fallback JNDI location [" + jndiName + "]");
return tm;
}
catch (TransactionSystemException ex) {
logger.debug("No JTA TransactionManager found at fallback JNDI location [" + jndiName + "]", ex);
}
}
// OK, so no JTA TransactionManager is available...
return null;
}
/**
* This implementation returns a JtaTransactionObject instance for the
* JTA UserTransaction.
*
The UserTransaction object will either be looked up freshly for the
* current transaction, or the cached one looked up at startup will be used.
* The latter is the default: Most application servers use a shared singleton
* UserTransaction that can be cached. Turn off the "cacheUserTransaction"
* flag to enforce a fresh lookup for every transaction.
* @see #setCacheUserTransaction
*/
protected Object doGetTransaction() {
UserTransaction ut = getUserTransaction();
if (!this.cacheUserTransaction && this.userTransactionName != null) {
ut = lookupUserTransaction(this.userTransactionName);
}
return doGetJtaTransaction(ut);
}
/**
* Get a JTA transaction object for the given current UserTransaction.
*
Subclasses can override this to provide a JtaTransactionObject
* subclass, for example holding some additional JTA handle needed.
* @param ut the UserTransaction handle to use for the current transaction
* @return the JtaTransactionObject holding the UserTransaction
*/
protected JtaTransactionObject doGetJtaTransaction(UserTransaction ut) {
return new JtaTransactionObject(ut);
}
protected boolean isExistingTransaction(Object transaction) {
JtaTransactionObject txObject = (JtaTransactionObject) transaction;
try {
return (txObject.getUserTransaction().getStatus() != Status.STATUS_NO_TRANSACTION);
}
catch (SystemException ex) {
throw new TransactionSystemException("JTA failure on getStatus", ex);
}
}
/**
* This implementation returns false to cause a further invocation
* of doBegin despite an already existing transaction.
*
JTA implementations might support nested transactions via further
* UserTransaction.begin
invocations, but never support savepoints.
* @see #doBegin
* @see javax.transaction.UserTransaction#begin
*/
protected boolean useSavepointForNestedTransaction() {
return false;
}
protected void doBegin(Object transaction, TransactionDefinition definition) {
JtaTransactionObject txObject = (JtaTransactionObject) transaction;
logger.debug("Beginning JTA transaction");
try {
doJtaBegin(txObject, definition);
}
catch (NotSupportedException ex) {
// assume nested transaction not supported
throw new NestedTransactionNotSupportedException(
"JTA implementation does not support nested transactions", ex);
}
catch (UnsupportedOperationException ex) {
// assume nested transaction not supported
throw new NestedTransactionNotSupportedException(
"JTA implementation does not support nested transactions", ex);
}
catch (SystemException ex) {
throw new CannotCreateTransactionException("JTA failure on begin", ex);
}
}
/**
* Perform a JTA begin on the JTA UserTransaction or TransactionManager.
*
This implementation only supports standard JTA functionality:
* that is, no per-transaction isolation levels and no transaction names.
* Can be overridden in subclasses, for specific JTA implementations.
*
Calls applyIsolationLevel
and applyTimeout
* before invoking the UserTransaction's begin
method.
* @param txObject the JtaTransactionObject containing the UserTransaction
* @param definition TransactionDefinition instance, describing propagation
* behavior, isolation level, read-only flag, timeout, and transaction name
* @throws NotSupportedException if thrown by JTA methods
* @throws SystemException if thrown by JTA methods
* @see #getUserTransaction
* @see #getTransactionManager
* @see #applyIsolationLevel
* @see #applyTimeout
* @see JtaTransactionObject#getUserTransaction()
* @see javax.transaction.UserTransaction#setTransactionTimeout
* @see javax.transaction.UserTransaction#begin
*/
protected void doJtaBegin(JtaTransactionObject txObject, TransactionDefinition definition)
throws NotSupportedException, SystemException {
applyIsolationLevel(txObject, definition.getIsolationLevel());
applyTimeout(txObject, definition.getTimeout());
txObject.getUserTransaction().begin();
}
/**
* Apply the given transaction isolation level. Default implementation
* will throw an exception for any level other than ISOLATION_DEFAULT.
*
To be overridden in subclasses for specific JTA implementations,
* as alternative to overriding the full doJtaBegin
method.
* @param txObject the JtaTransactionObject containing the UserTransaction
* @param isolationLevel isolation level taken from transaction definition
* @throws InvalidIsolationLevelException if the given isolation level
* cannot be applied
* @throws SystemException if thrown by the JTA implementation
* @see #doJtaBegin
* @see JtaTransactionObject#getUserTransaction()
* @see #getTransactionManager()
*/
protected void applyIsolationLevel(JtaTransactionObject txObject, int isolationLevel)
throws InvalidIsolationLevelException, SystemException {
if (isolationLevel != TransactionDefinition.ISOLATION_DEFAULT) {
throw new InvalidIsolationLevelException(
"JtaTransactionManager does not support custom isolation levels");
}
}
/**
* Apply the given transaction timeout. Default implementation will call
* setTransactionTimeout
for a non-default timeout value.
* @param txObject the JtaTransactionObject containing the UserTransaction
* @param timeout timeout value taken from transaction definition
* @throws SystemException if thrown by the JTA implementation
* @see #doJtaBegin
* @see JtaTransactionObject#getUserTransaction()
* @see javax.transaction.UserTransaction#setTransactionTimeout(int)
*/
protected void applyTimeout(JtaTransactionObject txObject, int timeout) throws SystemException {
if (timeout > TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getUserTransaction().setTransactionTimeout(timeout);
}
}
protected Object doSuspend(Object transaction) {
JtaTransactionObject txObject = (JtaTransactionObject) transaction;
logger.debug("Suspending JTA transaction");
try {
return doJtaSuspend(txObject);
}
catch (SystemException ex) {
throw new TransactionSystemException("JTA failure on suspend", ex);
}
}
/**
* Perform a JTA suspend on the JTA TransactionManager.
*
Can be overridden in subclasses, for specific JTA implementations.
* @param txObject the JtaTransactionObject containing the UserTransaction
* @return the suspended JTA Transaction object
* @throws SystemException if thrown by JTA methods
* @see #getTransactionManager()
* @see javax.transaction.TransactionManager#suspend()
*/
protected Object doJtaSuspend(JtaTransactionObject txObject) throws SystemException {
if (getTransactionManager() == null) {
throw new TransactionSuspensionNotSupportedException(
"JtaTransactionManager needs a JTA TransactionManager for suspending a transaction - " +
"specify the 'transactionManager' or 'transactionManagerName' property");
}
return getTransactionManager().suspend();
}
protected void doResume(Object transaction, Object suspendedResources) {
JtaTransactionObject txObject = (JtaTransactionObject) transaction;
logger.debug("Resuming JTA transaction");
try {
doJtaResume(txObject, suspendedResources);
}
catch (InvalidTransactionException ex) {
throw new IllegalTransactionStateException("Tried to resume invalid JTA transaction", ex);
}
catch (SystemException ex) {
throw new TransactionSystemException("JTA failure on resume", ex);
}
}
/**
* Perform a JTA resume on the JTA TransactionManager.
*
Can be overridden in subclasses, for specific JTA implementations.
* @param txObject the JtaTransactionObject containing the UserTransaction
* @param suspendedTransaction the suspended JTA Transaction object
* @throws InvalidTransactionException if thrown by JTA methods
* @throws SystemException if thrown by JTA methods
* @see #getTransactionManager()
* @see javax.transaction.TransactionManager#resume(javax.transaction.Transaction)
*/
protected void doJtaResume(JtaTransactionObject txObject, Object suspendedTransaction)
throws InvalidTransactionException, SystemException {
if (getTransactionManager() == null) {
throw new TransactionSuspensionNotSupportedException(
"JtaTransactionManager needs a JTA TransactionManager for suspending a transaction - " +
"specify the 'transactionManager' or 'transactionManagerName' property");
}
getTransactionManager().resume((Transaction) suspendedTransaction);
}
/**
* This implementation returns "true": a JTA commit will properly handle
* transactions that have been marked rollback-only at a global level.
*/
protected boolean shouldCommitOnGlobalRollbackOnly() {
return true;
}
protected void doCommit(DefaultTransactionStatus status) {
JtaTransactionObject txObject = (JtaTransactionObject) status.getTransaction();
logger.debug("Committing JTA transaction");
try {
txObject.getUserTransaction().commit();
}
catch (RollbackException ex) {
throw new UnexpectedRollbackException(
"JTA transaction unexpectedly rolled back (maybe due to a timeout)", ex);
}
catch (HeuristicMixedException ex) {
throw new HeuristicCompletionException(HeuristicCompletionException.STATE_MIXED, ex);
}
catch (HeuristicRollbackException ex) {
throw new HeuristicCompletionException(HeuristicCompletionException.STATE_ROLLED_BACK, ex);
}
catch (SystemException ex) {
throw new TransactionSystemException("JTA failure on commit", ex);
}
}
protected void doRollback(DefaultTransactionStatus status) {
JtaTransactionObject txObject = (JtaTransactionObject) status.getTransaction();
logger.debug("Rolling back JTA transaction");
try {
txObject.getUserTransaction().rollback();
}
catch (SystemException ex) {
throw new TransactionSystemException("JTA failure on rollback", ex);
}
}
protected void doSetRollbackOnly(DefaultTransactionStatus status) {
JtaTransactionObject txObject = (JtaTransactionObject) status.getTransaction();
if (status.isDebug()) {
logger.debug("Setting JTA transaction rollback-only");
}
try {
txObject.getUserTransaction().setRollbackOnly();
}
catch (IllegalStateException ex) {
throw new NoTransactionException("No active JTA transaction");
}
catch (SystemException ex) {
throw new TransactionSystemException("JTA failure on setRollbackOnly", ex);
}
}
protected void registerAfterCompletionWithExistingTransaction(Object transaction, List synchronizations) {
JtaTransactionObject txObject = (JtaTransactionObject) transaction;
logger.debug("Registering after-completion synchronization with existing JTA transaction");
try {
doRegisterAfterCompletionWithJtaTransaction(txObject, synchronizations);
}
catch (RollbackException ex) {
logger.debug("Participating in existing JTA transaction that has been marked rollback-only: " +
"cannot register Spring after-completion callbacks with outer JTA transaction - " +
"immediately performing Spring after-completion callbacks with outcome status 'rollback'");
invokeAfterCompletion(synchronizations, TransactionSynchronization.STATUS_ROLLED_BACK);
}
catch (IllegalStateException ex) {
throw new NoTransactionException("No active JTA transaction");
}
catch (SystemException ex) {
throw new TransactionSystemException("JTA failure on registerSynchronization", ex);
}
}
/**
* Register a JTA synchronization on the JTA TransactionManager, for calling
* afterCompletion
on the given Spring TransactionSynchronizations.
*
Can be overridden in subclasses, for specific JTA implementations.
* @param synchronizations List of TransactionSynchronization objects
* @throws RollbackException if thrown by JTA methods
* @throws SystemException if thrown by JTA methods
* @see #getTransactionManager()
* @see javax.transaction.Transaction#registerSynchronization
* @see #invokeAfterCompletion(java.util.List, int)
*/
protected void doRegisterAfterCompletionWithJtaTransaction(
JtaTransactionObject txObject, List synchronizations)
throws RollbackException, SystemException {
if (getTransactionManager() != null) {
getTransactionManager().getTransaction().registerSynchronization(
new JtaAfterCompletionSynchronization(synchronizations));
}
else {
// No JTA TransactionManager available.
logger.warn("Participating in existing JTA transaction, but no JTA TransactionManager available: " +
"cannot register Spring after-completion callbacks with outer JTA transaction - " +
"performing Spring after-completion callbacks with outcome status 'unknown'");
invokeAfterCompletion(synchronizations, TransactionSynchronization.STATUS_UNKNOWN);
}
}
//---------------------------------------------------------------------
// Serialization support
//---------------------------------------------------------------------
private void readObject(ObjectInputStream ois) throws IOException {
// Rely on default serialization, just initialize state after deserialization.
try {
ois.defaultReadObject();
}
catch (ClassNotFoundException ex) {
throw new IOException(
"Failed to deserialize JtaTransactionManager - check that JTA and Spring transaction " +
"libraries are available on the client side: " + ex.getMessage());
}
// Do client-side JNDI lookup.
this.jndiTemplate = new JndiTemplate();
// Perform lookup for JTA UserTransaction and TransactionManager.
if (this.userTransactionName != null) {
this.userTransaction = lookupUserTransaction(this.userTransactionName);
}
if (this.transactionManagerName != null) {
this.transactionManager = lookupTransactionManager(this.transactionManagerName);
}
}
/**
* Adapter for a JTA Synchronization, invoking the afterCompletion
of
* Spring TransactionSynchronizations after the outer JTA transaction has completed.
* Applied when participating in an existing (non-Spring) JTA transaction.
*/
private class JtaAfterCompletionSynchronization implements Synchronization {
private final List synchronizations;
public JtaAfterCompletionSynchronization(List synchronizations) {
this.synchronizations = synchronizations;
}
public void beforeCompletion() {
}
public void afterCompletion(int status) {
switch (status) {
case Status.STATUS_COMMITTED:
invokeAfterCompletion(this.synchronizations, TransactionSynchronization.STATUS_COMMITTED);
break;
case Status.STATUS_ROLLEDBACK:
invokeAfterCompletion(this.synchronizations, TransactionSynchronization.STATUS_ROLLED_BACK);
break;
default:
invokeAfterCompletion(this.synchronizations, TransactionSynchronization.STATUS_UNKNOWN);
}
}
}
}