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

org.springframework.transaction.jta.JtaTransactionManager Maven / Gradle / Ivy

There is a newer version: 6.2.0
Show newest version
/*
 * Copyright 2002-2023 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
 *
 *      https://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 jakarta.transaction.HeuristicMixedException;
import jakarta.transaction.HeuristicRollbackException;
import jakarta.transaction.InvalidTransactionException;
import jakarta.transaction.NotSupportedException;
import jakarta.transaction.RollbackException;
import jakarta.transaction.Status;
import jakarta.transaction.SystemException;
import jakarta.transaction.Transaction;
import jakarta.transaction.TransactionManager;
import jakarta.transaction.TransactionSynchronizationRegistry;
import jakarta.transaction.UserTransaction;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.jndi.JndiTemplate;
import org.springframework.lang.Nullable;
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.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;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
 * {@link org.springframework.transaction.PlatformTransactionManager} implementation
 * for JTA, delegating to a backend JTA provider. This is typically used to delegate
 * to a Jakarta EE server's transaction coordinator, but may also be configured with a
 * local JTA provider which is embedded within the application.
 *
 * 

This transaction manager is appropriate for handling distributed transactions, * i.e. transactions that span multiple resources, and for controlling transactions on * application server resources (e.g. JDBC DataSources available in JNDI) in general. * For a single JDBC DataSource, DataSourceTransactionManager is perfectly sufficient, * and for accessing a single resource with Hibernate (including transactional cache), * HibernateTransactionManager is appropriate, for example. * *

For typical JTA transactions (REQUIRED, SUPPORTS, MANDATORY, NEVER), a plain * JtaTransactionManager definition is all you need, portable across all Jakarta EE servers. * This corresponds to the functionality of the JTA UserTransaction, for which Jakarta EE * 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. * *

Transaction suspension (REQUIRES_NEW, NOT_SUPPORTED) is just available with a * JTA TransactionManager being registered. Common TransactionManager locations are * autodetected by JtaTransactionManager, provided that the "autodetectTransactionManager" * flag is set to "true" (which it is by default). * *

Note: Support for the JTA TransactionManager interface is not required by Jakarta EE. * Almost all Jakarta EE servers expose it, but do so as extension to EE. There might be some * issues with compatibility, despite the TransactionManager interface being part of JTA. * *

This pure JtaTransactionManager class supports timeouts but not per-transaction * isolation levels. Custom subclasses may override the {@link #doJtaBegin} method for * specific JTA extensions in order to provide this functionality. Such adapters for * specific Jakarta EE transaction coordinators may also expose transaction names for * monitoring; with standard JTA, transaction names will simply be ignored. * *

JTA 1.1 adds the TransactionSynchronizationRegistry facility, as public Jakarta EE * API in addition to the standard JTA UserTransaction handle. As of Spring 2.5, this * JtaTransactionManager autodetects the TransactionSynchronizationRegistry and uses * it for registering Spring-managed synchronizations when participating in an existing * JTA transaction (e.g. controlled by EJB CMT). If no TransactionSynchronizationRegistry * is available, then such synchronizations will be registered via the (non-EE) JTA * TransactionManager handle. * *

This class is serializable. However, active synchronizations do not survive serialization. * * @author Juergen Hoeller * @since 24.03.2003 * @see jakarta.transaction.UserTransaction * @see jakarta.transaction.TransactionManager * @see jakarta.transaction.TransactionSynchronizationRegistry * @see #setUserTransactionName * @see #setUserTransaction * @see #setTransactionManagerName * @see #setTransactionManager */ @SuppressWarnings("serial") public class JtaTransactionManager extends AbstractPlatformTransactionManager implements TransactionFactory, InitializingBean, Serializable { /** * Default JNDI location for the JTA UserTransaction. Many Jakarta EE 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:appserver/TransactionManager", "java:pm/TransactionManager", "java:/TransactionManager"}; /** * Standard Jakarta EE JNDI location for the JTA TransactionSynchronizationRegistry. * Autodetected when available. */ public static final String DEFAULT_TRANSACTION_SYNCHRONIZATION_REGISTRY_NAME = "java:comp/TransactionSynchronizationRegistry"; private transient JndiTemplate jndiTemplate = new JndiTemplate(); @Nullable private transient UserTransaction userTransaction; @Nullable private String userTransactionName; private boolean autodetectUserTransaction = true; private boolean cacheUserTransaction = true; private boolean userTransactionObtainedFromJndi = false; @Nullable private transient TransactionManager transactionManager; @Nullable private String transactionManagerName; private boolean autodetectTransactionManager = true; @Nullable private transient TransactionSynchronizationRegistry transactionSynchronizationRegistry; @Nullable private String transactionSynchronizationRegistryName; private boolean autodetectTransactionSynchronizationRegistry = true; private boolean allowCustomIsolationLevels = false; /** * Create a new JtaTransactionManager instance, to be configured as bean. * Invoke {@code 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(); Assert.notNull(userTransaction, "UserTransaction must not be null"); this.userTransaction = userTransaction; } /** * 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(); Assert.notNull(userTransaction, "UserTransaction must not be null"); Assert.notNull(transactionManager, "TransactionManager must not be null"); this.userTransaction = userTransaction; this.transactionManager = transactionManager; } /** * Create a new JtaTransactionManager instance. * @param transactionManager the JTA TransactionManager to use as direct reference */ public JtaTransactionManager(TransactionManager transactionManager) { this(); Assert.notNull(transactionManager, "TransactionManager must not be null"); this.transactionManager = transactionManager; this.userTransaction = buildUserTransaction(transactionManager); } /** * Set the JndiTemplate to use for JNDI lookups. * A default one is used if not set. */ public void setJndiTemplate(JndiTemplate jndiTemplate) { Assert.notNull(jndiTemplate, "JndiTemplate must not be null"); this.jndiTemplate = jndiTemplate; } /** * Return the JndiTemplate used for JNDI lookups. */ public JndiTemplate getJndiTemplate() { return this.jndiTemplate; } /** * Set the JNDI environment to use for JNDI lookups. * Creates a JndiTemplate with the given environment settings. * @see #setJndiTemplate */ public void setJndiEnvironment(@Nullable Properties jndiEnvironment) { this.jndiTemplate = new JndiTemplate(jndiEnvironment); } /** * Return the JNDI environment to use for JNDI lookups. */ @Nullable 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 Jakarta EE environment, * the UserTransaction will always be fetched from JNDI. * @see #setUserTransactionName * @see #setAutodetectUserTransaction */ public void setUserTransaction(@Nullable UserTransaction userTransaction) { this.userTransaction = userTransaction; } /** * Return the JTA UserTransaction that this transaction manager uses. */ @Nullable public UserTransaction getUserTransaction() { return this.userTransaction; } /** * Set the JNDI name of the JTA UserTransaction. *

Note that the UserTransaction will be autodetected at the Jakarta EE * default location "java:comp/UserTransaction" if not specified explicitly. * @see #DEFAULT_USER_TRANSACTION_NAME * @see #setUserTransaction * @see #setAutodetectUserTransaction */ public void setUserTransactionName(String userTransactionName) { this.userTransactionName = userTransactionName; } /** * Set whether to autodetect the JTA UserTransaction at its default * JNDI location "java:comp/UserTransaction", as specified by Jakarta EE. * Will proceed without UserTransaction if none found. *

Default is "true", autodetecting the UserTransaction unless * it has been specified explicitly. Turn this flag off to allow for * JtaTransactionManager operating against the TransactionManager only, * despite a default UserTransaction being available. * @see #DEFAULT_USER_TRANSACTION_NAME */ public void setAutodetectUserTransaction(boolean autodetectUserTransaction) { this.autodetectUserTransaction = autodetectUserTransaction; } /** * 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. * @see #setUserTransactionName */ 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, * as well as autodetected at various well-known fallback JNDI locations. * @see #setTransactionManagerName * @see #setAutodetectTransactionManager */ public void setTransactionManager(@Nullable TransactionManager transactionManager) { this.transactionManager = transactionManager; } /** * Return the JTA TransactionManager that this transaction manager uses, if any. */ @Nullable public TransactionManager getTransactionManager() { return this.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, * as well as autodetected at various well-known fallback JNDI locations. * @see #setTransactionManager * @see #setAutodetectTransactionManager */ 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", autodetecting the TransactionManager unless it has been * specified explicitly. 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; } /** * Set the JTA 1.1 TransactionSynchronizationRegistry to use as direct reference. *

A TransactionSynchronizationRegistry allows for interposed registration * of transaction synchronizations, as an alternative to the regular registration * methods on the JTA TransactionManager API. Also, it is an official part of the * Jakarta EE platform, in contrast to the JTA TransactionManager itself. *

Note that the TransactionSynchronizationRegistry will be autodetected in JNDI and * also from the UserTransaction/TransactionManager object if implemented there as well. * @see #setTransactionSynchronizationRegistryName * @see #setAutodetectTransactionSynchronizationRegistry */ public void setTransactionSynchronizationRegistry(@Nullable TransactionSynchronizationRegistry transactionSynchronizationRegistry) { this.transactionSynchronizationRegistry = transactionSynchronizationRegistry; } /** * Return the JTA 1.1 TransactionSynchronizationRegistry that this transaction manager uses, if any. */ @Nullable public TransactionSynchronizationRegistry getTransactionSynchronizationRegistry() { return this.transactionSynchronizationRegistry; } /** * Set the JNDI name of the JTA 1.1 TransactionSynchronizationRegistry. *

Note that the TransactionSynchronizationRegistry will be autodetected * at the Jakarta EE default location "java:comp/TransactionSynchronizationRegistry" * if not specified explicitly. * @see #DEFAULT_TRANSACTION_SYNCHRONIZATION_REGISTRY_NAME */ public void setTransactionSynchronizationRegistryName(String transactionSynchronizationRegistryName) { this.transactionSynchronizationRegistryName = transactionSynchronizationRegistryName; } /** * Set whether to autodetect a JTA 1.1 TransactionSynchronizationRegistry object * at its default JDNI location ("java:comp/TransactionSynchronizationRegistry") * if the UserTransaction has also been obtained from JNDI, and also whether * to fall back to checking whether the JTA UserTransaction/TransactionManager * object implements the JTA TransactionSynchronizationRegistry interface too. *

Default is "true", autodetecting the TransactionSynchronizationRegistry * unless it has been specified explicitly. Can be turned off to delegate * synchronization registration to the regular JTA TransactionManager API. */ public void setAutodetectTransactionSynchronizationRegistry(boolean autodetectTransactionSynchronizationRegistry) { this.autodetectTransactionSynchronizationRegistry = autodetectTransactionSynchronizationRegistry; } /** * Set whether to allow custom isolation levels to be specified. *

Default is "false", throwing an exception if a non-default isolation level * is specified for a transaction. Turn this flag on if affected resource adapters * check the thread-bound transaction context and apply the specified isolation * levels individually (e.g. through an IsolationLevelDataSourceAdapter). * @see org.springframework.jdbc.datasource.IsolationLevelDataSourceAdapter * @see org.springframework.jdbc.datasource.lookup.IsolationLevelDataSourceRouter */ public void setAllowCustomIsolationLevels(boolean allowCustomIsolationLevels) { this.allowCustomIsolationLevels = allowCustomIsolationLevels; } /** * Initialize the UserTransaction as well as the TransactionManager handle. * @see #initUserTransactionAndTransactionManager() */ @Override public void afterPropertiesSet() throws TransactionSystemException { initUserTransactionAndTransactionManager(); checkUserTransactionAndTransactionManager(); initTransactionSynchronizationRegistry(); } /** * Initialize the UserTransaction as well as the TransactionManager handle. * @throws TransactionSystemException if initialization failed */ protected void initUserTransactionAndTransactionManager() throws TransactionSystemException { if (this.userTransaction == null) { // Fetch JTA UserTransaction from JNDI, if necessary. if (StringUtils.hasLength(this.userTransactionName)) { this.userTransaction = lookupUserTransaction(this.userTransactionName); this.userTransactionObtainedFromJndi = true; } else { this.userTransaction = retrieveUserTransaction(); if (this.userTransaction == null && this.autodetectUserTransaction) { // Autodetect UserTransaction at its default JNDI location. this.userTransaction = findUserTransaction(); } } } if (this.transactionManager == null) { // Fetch JTA TransactionManager from JNDI, if necessary. if (StringUtils.hasLength(this.transactionManagerName)) { this.transactionManager = lookupTransactionManager(this.transactionManagerName); } else { this.transactionManager = retrieveTransactionManager(); if (this.transactionManager == null && this.autodetectTransactionManager) { // Autodetect UserTransaction object that implements TransactionManager, // and check fallback JNDI locations otherwise. this.transactionManager = findTransactionManager(this.userTransaction); } } } // If only JTA TransactionManager specified, create UserTransaction handle for it. if (this.userTransaction == null && this.transactionManager != null) { this.userTransaction = buildUserTransaction(this.transactionManager); } } /** * Check the UserTransaction as well as the TransactionManager handle, * assuming standard JTA requirements. * @throws IllegalStateException if no sufficient handles are available */ protected void checkUserTransactionAndTransactionManager() throws IllegalStateException { // We at least need the JTA UserTransaction. if (this.userTransaction != null) { if (logger.isDebugEnabled()) { logger.debug("Using JTA UserTransaction: " + this.userTransaction); } } else { throw new IllegalStateException("No JTA UserTransaction available - specify either " + "'userTransaction' or 'userTransactionName' or 'transactionManager' or 'transactionManagerName'"); } // For transaction suspension, the JTA TransactionManager is necessary too. if (this.transactionManager != null) { if (logger.isDebugEnabled()) { logger.debug("Using JTA TransactionManager: " + this.transactionManager); } } else { logger.warn("No JTA TransactionManager found: transaction suspension not available"); } } /** * Initialize the JTA 1.1 TransactionSynchronizationRegistry, if available. *

To be called after {@link #initUserTransactionAndTransactionManager()}, * since it may check the UserTransaction and TransactionManager handles. * @throws TransactionSystemException if initialization failed */ protected void initTransactionSynchronizationRegistry() { if (this.transactionSynchronizationRegistry == null) { // Fetch JTA TransactionSynchronizationRegistry from JNDI, if necessary. if (StringUtils.hasLength(this.transactionSynchronizationRegistryName)) { this.transactionSynchronizationRegistry = lookupTransactionSynchronizationRegistry(this.transactionSynchronizationRegistryName); } else { this.transactionSynchronizationRegistry = retrieveTransactionSynchronizationRegistry(); if (this.transactionSynchronizationRegistry == null && this.autodetectTransactionSynchronizationRegistry) { // Autodetect in JNDI if applicable, and check UserTransaction/TransactionManager // object that implements TransactionSynchronizationRegistry otherwise. this.transactionSynchronizationRegistry = findTransactionSynchronizationRegistry(this.userTransaction, this.transactionManager); } } } if (this.transactionSynchronizationRegistry != null) { if (logger.isDebugEnabled()) { logger.debug("Using JTA TransactionSynchronizationRegistry: " + this.transactionSynchronizationRegistry); } } } /** * Build a UserTransaction handle based on the given TransactionManager. * @param transactionManager the TransactionManager * @return a corresponding UserTransaction handle */ protected UserTransaction buildUserTransaction(TransactionManager transactionManager) { if (transactionManager instanceof UserTransaction ut) { return ut; } else { return new UserTransactionAdapter(transactionManager); } } /** * Look up the JTA UserTransaction in JNDI via the configured name. *

Called by {@code 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 + "]"); } return getJndiTemplate().lookup(userTransactionName, UserTransaction.class); } 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 {@code 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 + "]"); } return getJndiTemplate().lookup(transactionManagerName, TransactionManager.class); } catch (NamingException ex) { throw new TransactionSystemException( "JTA TransactionManager is not available at JNDI location [" + transactionManagerName + "]", ex); } } /** * Look up the JTA 1.1 TransactionSynchronizationRegistry in JNDI via the configured name. *

Can be overridden in subclasses to provide a different TransactionManager object. * @param registryName the JNDI name of the * TransactionSynchronizationRegistry * @return the TransactionSynchronizationRegistry object * @throws TransactionSystemException if the JNDI lookup failed * @see #setJndiTemplate * @see #setTransactionSynchronizationRegistryName */ protected TransactionSynchronizationRegistry lookupTransactionSynchronizationRegistry(String registryName) throws TransactionSystemException { try { if (logger.isDebugEnabled()) { logger.debug("Retrieving JTA TransactionSynchronizationRegistry from JNDI location [" + registryName + "]"); } return getJndiTemplate().lookup(registryName, TransactionSynchronizationRegistry.class); } catch (NamingException ex) { throw new TransactionSystemException( "JTA TransactionSynchronizationRegistry is not available at JNDI location [" + registryName + "]", ex); } } /** * Allows subclasses to retrieve the JTA UserTransaction in a vendor-specific manner. * Only called if no "userTransaction" or "userTransactionName" specified. *

The default implementation simply returns {@code null}. * @return the JTA UserTransaction handle to use, or {@code null} if none found * @throws TransactionSystemException in case of errors * @see #setUserTransaction * @see #setUserTransactionName */ @Nullable 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. *

The default implementation simply returns {@code null}. * @return the JTA TransactionManager handle to use, or {@code null} if none found * @throws TransactionSystemException in case of errors * @see #setTransactionManager * @see #setTransactionManagerName */ @Nullable protected TransactionManager retrieveTransactionManager() throws TransactionSystemException { return null; } /** * Allows subclasses to retrieve the JTA 1.1 TransactionSynchronizationRegistry * in a vendor-specific manner. *

The default implementation simply returns {@code null}. * @return the JTA TransactionSynchronizationRegistry handle to use, * or {@code null} if none found * @throws TransactionSystemException in case of errors */ @Nullable protected TransactionSynchronizationRegistry retrieveTransactionSynchronizationRegistry() throws TransactionSystemException { return null; } /** * Find the JTA UserTransaction through a default JNDI lookup: * "java:comp/UserTransaction". * @return the JTA UserTransaction reference, or {@code null} if not found * @see #DEFAULT_USER_TRANSACTION_NAME */ @Nullable protected UserTransaction findUserTransaction() { String jndiName = DEFAULT_USER_TRANSACTION_NAME; try { UserTransaction ut = getJndiTemplate().lookup(jndiName, UserTransaction.class); if (logger.isDebugEnabled()) { logger.debug("JTA UserTransaction found at default JNDI location [" + jndiName + "]"); } this.userTransactionObtainedFromJndi = true; return ut; } catch (NamingException ex) { if (logger.isDebugEnabled()) { logger.debug("No JTA UserTransaction found at default JNDI location [" + jndiName + "]", ex); } 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 {@code null} if not found * @see #FALLBACK_TRANSACTION_MANAGER_NAMES */ @Nullable protected TransactionManager findTransactionManager(@Nullable UserTransaction ut) { if (ut instanceof TransactionManager tm) { if (logger.isDebugEnabled()) { logger.debug("JTA UserTransaction object [" + ut + "] implements TransactionManager"); } return tm; } // Check fallback JNDI locations. for (String jndiName : FALLBACK_TRANSACTION_MANAGER_NAMES) { try { TransactionManager tm = getJndiTemplate().lookup(jndiName, TransactionManager.class); if (logger.isDebugEnabled()) { logger.debug("JTA TransactionManager found at fallback JNDI location [" + jndiName + "]"); } return tm; } catch (NamingException ex) { if (logger.isDebugEnabled()) { logger.debug("No JTA TransactionManager found at fallback JNDI location [" + jndiName + "]", ex); } } } // OK, so no JTA TransactionManager is available... return null; } /** * Find the JTA 1.1 TransactionSynchronizationRegistry through autodetection: * checking whether the UserTransaction object or TransactionManager object * implements it, and checking Jakarta EE's standard JNDI location. *

The default implementation simply returns {@code null}. * @param ut the JTA UserTransaction object * @param tm the JTA TransactionManager object * @return the JTA TransactionSynchronizationRegistry handle to use, * or {@code null} if none found * @throws TransactionSystemException in case of errors */ @Nullable protected TransactionSynchronizationRegistry findTransactionSynchronizationRegistry( @Nullable UserTransaction ut, @Nullable TransactionManager tm) throws TransactionSystemException { if (this.userTransactionObtainedFromJndi) { // UserTransaction has already been obtained from JNDI, so the // TransactionSynchronizationRegistry probably sits there as well. String jndiName = DEFAULT_TRANSACTION_SYNCHRONIZATION_REGISTRY_NAME; try { TransactionSynchronizationRegistry tsr = getJndiTemplate().lookup(jndiName, TransactionSynchronizationRegistry.class); if (logger.isDebugEnabled()) { logger.debug("JTA TransactionSynchronizationRegistry found at default JNDI location [" + jndiName + "]"); } return tsr; } catch (NamingException ex) { if (logger.isDebugEnabled()) { logger.debug("No JTA TransactionSynchronizationRegistry found at default JNDI location [" + jndiName + "]", ex); } } } // Check whether the UserTransaction or TransactionManager implements it... if (ut instanceof TransactionSynchronizationRegistry tsr) { return tsr; } if (tm instanceof TransactionSynchronizationRegistry tsr) { return tsr; } // OK, so no JTA 1.1 TransactionSynchronizationRegistry 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 */ @Override protected Object doGetTransaction() { UserTransaction ut = getUserTransaction(); if (ut == null) { throw new CannotCreateTransactionException("No JTA UserTransaction available - " + "programmatic PlatformTransactionManager.getTransaction usage not supported"); } if (!this.cacheUserTransaction) { ut = lookupUserTransaction( this.userTransactionName != null ? this.userTransactionName : DEFAULT_USER_TRANSACTION_NAME); } 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); } @Override 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 * {@code UserTransaction.begin()} invocations, but never support savepoints. * @see #doBegin * @see jakarta.transaction.UserTransaction#begin() */ @Override protected boolean useSavepointForNestedTransaction() { return false; } @Override protected void doBegin(Object transaction, TransactionDefinition definition) { JtaTransactionObject txObject = (JtaTransactionObject) transaction; try { doJtaBegin(txObject, definition); } catch (NotSupportedException | UnsupportedOperationException ex) { 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 {@code applyIsolationLevel} and {@code applyTimeout} * before invoking the UserTransaction's {@code begin} method. * @param txObject the JtaTransactionObject containing the UserTransaction * @param definition the 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 jakarta.transaction.UserTransaction#setTransactionTimeout * @see jakarta.transaction.UserTransaction#begin */ protected void doJtaBegin(JtaTransactionObject txObject, TransactionDefinition definition) throws NotSupportedException, SystemException { applyIsolationLevel(txObject, definition.getIsolationLevel()); int timeout = determineTimeout(definition); applyTimeout(txObject, timeout); txObject.getUserTransaction().begin(); } /** * Apply the given transaction isolation level. The 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 {@link #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 (!this.allowCustomIsolationLevels && isolationLevel != TransactionDefinition.ISOLATION_DEFAULT) { throw new InvalidIsolationLevelException( "JtaTransactionManager does not support custom isolation levels by default - " + "switch 'allowCustomIsolationLevels' to 'true'"); } } /** * Apply the given transaction timeout. The default implementation will call * {@code UserTransaction.setTransactionTimeout} for a non-default timeout value. * @param txObject the JtaTransactionObject containing the UserTransaction * @param timeout the timeout value taken from transaction definition * @throws SystemException if thrown by the JTA implementation * @see #doJtaBegin * @see JtaTransactionObject#getUserTransaction() * @see jakarta.transaction.UserTransaction#setTransactionTimeout(int) */ protected void applyTimeout(JtaTransactionObject txObject, int timeout) throws SystemException { if (timeout > TransactionDefinition.TIMEOUT_DEFAULT) { txObject.getUserTransaction().setTransactionTimeout(timeout); if (timeout > 0) { txObject.resetTransactionTimeout = true; } } } @Override protected Object doSuspend(Object transaction) { JtaTransactionObject txObject = (JtaTransactionObject) 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 jakarta.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(); } @Override protected void doResume(@Nullable Object transaction, Object suspendedResources) { JtaTransactionObject txObject = (JtaTransactionObject) transaction; try { doJtaResume(txObject, suspendedResources); } catch (InvalidTransactionException ex) { throw new IllegalTransactionStateException("Tried to resume invalid JTA transaction", ex); } catch (IllegalStateException ex) { throw new TransactionSystemException("Unexpected internal transaction state", 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 jakarta.transaction.TransactionManager#resume(jakarta.transaction.Transaction) */ protected void doJtaResume(@Nullable 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. */ @Override protected boolean shouldCommitOnGlobalRollbackOnly() { return true; } @Override protected void doCommit(DefaultTransactionStatus status) { JtaTransactionObject txObject = (JtaTransactionObject) status.getTransaction(); try { int jtaStatus = txObject.getUserTransaction().getStatus(); if (jtaStatus == Status.STATUS_NO_TRANSACTION) { // Should never happen... would have thrown an exception before // and as a consequence led to a rollback, not to a commit call. // In any case, the transaction is already fully cleaned up. throw new UnexpectedRollbackException("JTA transaction already completed - probably rolled back"); } if (jtaStatus == Status.STATUS_ROLLEDBACK) { // Only really happens on JBoss 4.2 in case of an early timeout... // Explicit rollback call necessary to clean up the transaction. // IllegalStateException expected on JBoss; call still necessary. try { txObject.getUserTransaction().rollback(); } catch (IllegalStateException ex) { if (logger.isDebugEnabled()) { logger.debug("Rollback failure with transaction already marked as rolled back: " + ex); } } throw new UnexpectedRollbackException("JTA transaction already rolled back (probably due to a timeout)"); } 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 (IllegalStateException ex) { throw new TransactionSystemException("Unexpected internal transaction state", ex); } catch (SystemException ex) { throw new TransactionSystemException("JTA failure on commit", ex); } } @Override protected void doRollback(DefaultTransactionStatus status) { JtaTransactionObject txObject = (JtaTransactionObject) status.getTransaction(); try { int jtaStatus = txObject.getUserTransaction().getStatus(); if (jtaStatus != Status.STATUS_NO_TRANSACTION) { try { txObject.getUserTransaction().rollback(); } catch (IllegalStateException ex) { if (jtaStatus == Status.STATUS_ROLLEDBACK) { // Only really happens on JBoss 4.2 in case of an early timeout... if (logger.isDebugEnabled()) { logger.debug("Rollback failure with transaction already marked as rolled back: " + ex); } } else { throw new TransactionSystemException("Unexpected internal transaction state", ex); } } } } catch (SystemException ex) { throw new TransactionSystemException("JTA failure on rollback", ex); } } @Override protected void doSetRollbackOnly(DefaultTransactionStatus status) { JtaTransactionObject txObject = (JtaTransactionObject) status.getTransaction(); if (status.isDebug()) { logger.debug("Setting JTA transaction rollback-only"); } try { int jtaStatus = txObject.getUserTransaction().getStatus(); if (jtaStatus != Status.STATUS_NO_TRANSACTION && jtaStatus != Status.STATUS_ROLLEDBACK) { txObject.getUserTransaction().setRollbackOnly(); } } catch (IllegalStateException ex) { throw new TransactionSystemException("Unexpected internal transaction state", ex); } catch (SystemException ex) { throw new TransactionSystemException("JTA failure on setRollbackOnly", ex); } } @Override 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 (SystemException ex) { throw new TransactionSystemException("JTA failure on registerSynchronization", ex); } catch (Exception ex) { // Note: JBoss throws plain RuntimeException with RollbackException as cause. if (ex instanceof RollbackException || ex.getCause() instanceof RollbackException) { logger.debug("Participating in existing JTA transaction that has been marked for rollback: " + "cannot register Spring after-completion callbacks with outer JTA transaction - " + "immediately performing Spring after-completion callbacks with outcome status 'rollback'. " + "Original exception: " + ex); invokeAfterCompletion(synchronizations, TransactionSynchronization.STATUS_ROLLED_BACK); } else { logger.debug("Participating in existing JTA transaction, but unexpected internal transaction " + "state encountered: cannot register Spring after-completion callbacks with outer JTA " + "transaction - processing Spring after-completion callbacks with outcome status 'unknown'" + "Original exception: " + ex); invokeAfterCompletion(synchronizations, TransactionSynchronization.STATUS_UNKNOWN); } } } /** * Register a JTA synchronization on the JTA TransactionManager, for calling * {@code afterCompletion} on the given Spring TransactionSynchronizations. *

The default implementation registers the synchronizations on the * JTA 1.1 TransactionSynchronizationRegistry, if available, or on the * JTA TransactionManager's current Transaction - again, if available. * If none of the two is available, a warning will be logged. *

Can be overridden in subclasses, for specific JTA implementations. * @param txObject the current transaction object * @param synchronizations a List of TransactionSynchronization objects * @throws RollbackException if thrown by JTA methods * @throws SystemException if thrown by JTA methods * @see #getTransactionManager() * @see jakarta.transaction.Transaction#registerSynchronization * @see jakarta.transaction.TransactionSynchronizationRegistry#registerInterposedSynchronization */ protected void doRegisterAfterCompletionWithJtaTransaction( JtaTransactionObject txObject, List synchronizations) throws RollbackException, SystemException { int jtaStatus = txObject.getUserTransaction().getStatus(); if (jtaStatus == Status.STATUS_NO_TRANSACTION) { throw new RollbackException("JTA transaction already completed - probably rolled back"); } if (jtaStatus == Status.STATUS_ROLLEDBACK) { throw new RollbackException("JTA transaction already rolled back (probably due to a timeout)"); } if (this.transactionSynchronizationRegistry != null) { // JTA 1.1 TransactionSynchronizationRegistry available - use it. this.transactionSynchronizationRegistry.registerInterposedSynchronization( new JtaAfterCompletionSynchronization(synchronizations)); } else if (getTransactionManager() != null) { // At least the JTA TransactionManager available - use that one. Transaction transaction = getTransactionManager().getTransaction(); if (transaction == null) { throw new IllegalStateException("No JTA Transaction available"); } transaction.registerSynchronization(new JtaAfterCompletionSynchronization(synchronizations)); } else { // No JTA TransactionManager available - log a warning. logger.warn("Participating in existing JTA transaction, but no JTA TransactionManager available: " + "cannot register Spring after-completion callbacks with outer JTA transaction - " + "processing Spring after-completion callbacks with outcome status 'unknown'"); invokeAfterCompletion(synchronizations, TransactionSynchronization.STATUS_UNKNOWN); } } @Override protected void doCleanupAfterCompletion(Object transaction) { JtaTransactionObject txObject = (JtaTransactionObject) transaction; if (txObject.resetTransactionTimeout) { try { txObject.getUserTransaction().setTransactionTimeout(0); } catch (SystemException ex) { logger.debug("Failed to reset transaction timeout after JTA completion", ex); } } } //--------------------------------------------------------------------- // Implementation of TransactionFactory interface //--------------------------------------------------------------------- @Override public Transaction createTransaction(@Nullable String name, int timeout) throws NotSupportedException, SystemException { TransactionManager tm = getTransactionManager(); Assert.state(tm != null, "No JTA TransactionManager available"); if (timeout >= 0) { tm.setTransactionTimeout(timeout); } tm.begin(); return new ManagedTransactionAdapter(tm); } @Override public boolean supportsResourceAdapterManagedTransactions() { return false; } //--------------------------------------------------------------------- // Serialization support //--------------------------------------------------------------------- private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { // Rely on default serialization; just initialize state after deserialization. ois.defaultReadObject(); // Create template for client-side JNDI lookup. this.jndiTemplate = new JndiTemplate(); // Perform a fresh lookup for JTA handles. initUserTransactionAndTransactionManager(); initTransactionSynchronizationRegistry(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy