HHH-3358 : non-JNDI distributed transaction support

git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@14895 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
Steve Ebersole 2008-07-08 15:38:44 +00:00
parent 6db1fbcec5
commit 89ed8071d4
2 changed files with 163 additions and 81 deletions

View File

@ -23,8 +23,6 @@
*/ */
package org.hibernate.transaction; package org.hibernate.transaction;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.transaction.Status; import javax.transaction.Status;
import javax.transaction.Synchronization; import javax.transaction.Synchronization;
import javax.transaction.SystemException; import javax.transaction.SystemException;
@ -33,7 +31,6 @@ import javax.transaction.UserTransaction;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.hibernate.AssertionFailure;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.Transaction; import org.hibernate.Transaction;
import org.hibernate.TransactionException; import org.hibernate.TransactionException;
@ -45,17 +42,10 @@ import org.hibernate.util.JTAHelper;
* a JTA {@link UserTransaction}. Similar to {@link CMTTransaction}, except * a JTA {@link UserTransaction}. Similar to {@link CMTTransaction}, except
* here we are actually managing the transactions through the Hibernate * here we are actually managing the transactions through the Hibernate
* transaction mechanism. * transaction mechanism.
* <p/>
* Instances check to see if there is an existing JTA transaction. If none
* exists, a new transaction is started; if one exists, all work is done in the
* existing context. The following properties are used to locate the underlying
* {@link UserTransaction}:<ul>
* <li><tt>hibernate.jndi.url</tt> : JNDI initial context URL</li>
* <li><tt>hibernate.jndi.class</tt> : JNDI provider class</li>
* <li><tt>jta.UserTransaction</tt> : JNDI namespace</li>
* </ul>
* *
* @author Gavin King * @author Gavin King
* @author Steve Ebersole
* @author Les Hazlewood
*/ */
public class JTATransaction implements Transaction { public class JTATransaction implements Transaction {
@ -64,39 +54,23 @@ public class JTATransaction implements Transaction {
private final JDBCContext jdbcContext; private final JDBCContext jdbcContext;
private final TransactionFactory.Context transactionContext; private final TransactionFactory.Context transactionContext;
private UserTransaction ut; private UserTransaction userTransaction;
private boolean newTransaction; private boolean newTransaction;
private boolean begun; private boolean begun;
private boolean commitFailed; private boolean commitFailed;
private boolean commitSucceeded; private boolean commitSucceeded;
private boolean callback; private boolean callback;
public JTATransaction( public JTATransaction(
InitialContext context, UserTransaction userTransaction,
String utName, JDBCContext jdbcContext,
JDBCContext jdbcContext, TransactionFactory.Context transactionContext) {
TransactionFactory.Context transactionContext
) {
this.jdbcContext = jdbcContext; this.jdbcContext = jdbcContext;
this.transactionContext = transactionContext; this.transactionContext = transactionContext;
this.userTransaction = userTransaction;
log.debug("Looking for UserTransaction under: " + utName);
try {
ut = (UserTransaction) context.lookup(utName);
}
catch (NamingException ne) {
log.error("Could not find UserTransaction in JNDI", ne);
throw new TransactionException("Could not find UserTransaction in JNDI: ", ne);
}
if (ut==null) {
throw new AssertionFailure("A naming service lookup returned null");
}
log.debug("Obtained UserTransaction");
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public void begin() throws HibernateException { public void begin() throws HibernateException {
@ -106,13 +80,13 @@ public class JTATransaction implements Transaction {
if (commitFailed) { if (commitFailed) {
throw new TransactionException("cannot re-start transaction after failed commit"); throw new TransactionException("cannot re-start transaction after failed commit");
} }
log.debug("begin"); log.debug("begin");
try { try {
newTransaction = ut.getStatus() == Status.STATUS_NO_TRANSACTION; newTransaction = userTransaction.getStatus() == Status.STATUS_NO_TRANSACTION;
if (newTransaction) { if (newTransaction) {
ut.begin(); userTransaction.begin();
log.debug("Began a new JTA transaction"); log.debug("Began a new JTA transaction");
} }
} }
@ -146,7 +120,7 @@ public class JTATransaction implements Transaction {
begun = true; begun = true;
commitSucceeded = false; commitSucceeded = false;
jdbcContext.afterTransactionBegin(this); jdbcContext.afterTransactionBegin(this);
} }
@ -175,7 +149,7 @@ public class JTATransaction implements Transaction {
if (newTransaction) { if (newTransaction) {
try { try {
ut.commit(); userTransaction.commit();
commitSucceeded = true; commitSucceeded = true;
log.debug("Committed JTA UserTransaction"); log.debug("Committed JTA UserTransaction");
} }
@ -223,12 +197,12 @@ public class JTATransaction implements Transaction {
try { try {
if (newTransaction) { if (newTransaction) {
if (!commitFailed) { if (!commitFailed) {
ut.rollback(); userTransaction.rollback();
log.debug("Rolled back JTA UserTransaction"); log.debug("Rolled back JTA UserTransaction");
} }
} }
else { else {
ut.setRollbackOnly(); userTransaction.setRollbackOnly();
log.debug("set JTA UserTransaction to rollback only"); log.debug("set JTA UserTransaction to rollback only");
} }
} }
@ -244,7 +218,7 @@ public class JTATransaction implements Transaction {
private static final int NULL = Integer.MIN_VALUE; private static final int NULL = Integer.MIN_VALUE;
private void afterCommitRollback() throws TransactionException { private void afterCommitRollback() throws TransactionException {
begun = false; begun = false;
if (callback) { // this method is a noop if there is a Synchronization! if (callback) { // this method is a noop if there is a Synchronization!
@ -254,7 +228,7 @@ public class JTATransaction implements Transaction {
} }
int status=NULL; int status=NULL;
try { try {
status = ut.getStatus(); status = userTransaction.getStatus();
} }
catch (Exception e) { catch (Exception e) {
log.error("Could not determine transaction status after commit", e); log.error("Could not determine transaction status after commit", e);
@ -275,13 +249,9 @@ public class JTATransaction implements Transaction {
* {@inheritDoc} * {@inheritDoc}
*/ */
public boolean wasRolledBack() throws TransactionException { public boolean wasRolledBack() throws TransactionException {
//if (!begun) return false;
//if (commitFailed) return true;
final int status; final int status;
try { try {
status = ut.getStatus(); status = userTransaction.getStatus();
} }
catch (SystemException se) { catch (SystemException se) {
log.error("Could not determine transaction status", se); log.error("Could not determine transaction status", se);
@ -299,12 +269,9 @@ public class JTATransaction implements Transaction {
* {@inheritDoc} * {@inheritDoc}
*/ */
public boolean wasCommitted() throws TransactionException { public boolean wasCommitted() throws TransactionException {
//if (!begun || commitFailed) return false;
final int status; final int status;
try { try {
status = ut.getStatus(); status = userTransaction.getStatus();
} }
catch (SystemException se) { catch (SystemException se) {
log.error("Could not determine transaction status", se); log.error("Could not determine transaction status", se);
@ -317,7 +284,7 @@ public class JTATransaction implements Transaction {
return status==Status.STATUS_COMMITTED; return status==Status.STATUS_COMMITTED;
} }
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
@ -327,7 +294,7 @@ public class JTATransaction implements Transaction {
final int status; final int status;
try { try {
status = ut.getStatus(); status = userTransaction.getStatus();
} }
catch (SystemException se) { catch (SystemException se) {
log.error("Could not determine transaction status", se); log.error("Could not determine transaction status", se);
@ -368,8 +335,8 @@ public class JTATransaction implements Transaction {
} }
private void closeIfRequired() throws HibernateException { private void closeIfRequired() throws HibernateException {
boolean close = callback && boolean close = callback &&
transactionContext.shouldAutoClose() && transactionContext.shouldAutoClose() &&
!transactionContext.isClosed(); !transactionContext.isClosed();
if ( close ) { if ( close ) {
transactionContext.managedClose(); transactionContext.managedClose();
@ -381,7 +348,7 @@ public class JTATransaction implements Transaction {
*/ */
public void setTimeout(int seconds) { public void setTimeout(int seconds) {
try { try {
ut.setTransactionTimeout(seconds); userTransaction.setTransactionTimeout(seconds);
} }
catch (SystemException se) { catch (SystemException se) {
throw new TransactionException("could not set transaction timeout", se); throw new TransactionException("could not set transaction timeout", se);
@ -394,6 +361,6 @@ public class JTATransaction implements Transaction {
* @return Value for property 'userTransaction'. * @return Value for property 'userTransaction'.
*/ */
protected UserTransaction getUserTransaction() { protected UserTransaction getUserTransaction() {
return ut; return userTransaction;
} }
} }

View File

@ -44,57 +44,177 @@ import org.hibernate.util.JTAHelper;
/** /**
* Factory for {@link JTATransaction} instances. * Factory for {@link JTATransaction} instances.
* <p/>
* To be completely accurate to the JTA spec, JTA implementations should
* publish their contextual {@link UserTransaction} reference into JNDI.
* However, in practice there are quite a few <tt>stand-alone</tt>
* implementations intended for use outside of J2EE/JEE containers and
* which therefore do not publish their {@link UserTransaction} references
* into JNDI but which otherwise follow the aspects of the JTA specification.
* This {@link TransactionFactory} implementation can support both models.
* <p/>
* For complete JTA implementations (including dependence on JNDI), the
* {@link UserTransaction} reference is obtained by a call to
* {@link #resolveInitialContext}. Hibernate will then attempt to locate the
* {@link UserTransaction} within this resolved
* {@link InitialContext} based on the namespace returned by
* {@link #resolveUserTransactionName}.
* <p/>
* For the so-called <tt>stand-alone</tt> implementations, we do not care at
* all about the JNDI aspects just described. Here, the implementation would
* have a specific manner to obtain a reference to its contextual
* {@link UserTransaction}; usually this would be a static code reference, but
* again it varies. Anyway, for each implementation the integration would need
* to override the {@link #getUserTransaction} method and return the appropriate
* thing.
* *
* @author Gavin King * @author Gavin King
* @author Steve Ebersole
* @author Les Hazlewood
*/ */
public class JTATransactionFactory implements TransactionFactory { public class JTATransactionFactory implements TransactionFactory {
public static final String DEFAULT_USER_TRANSACTION_NAME = "java:comp/UserTransaction";
private static final Logger log = LoggerFactory.getLogger( JTATransactionFactory.class ); private static final Logger log = LoggerFactory.getLogger( JTATransactionFactory.class );
private static final String DEFAULT_USER_TRANSACTION_NAME = "java:comp/UserTransaction";
protected InitialContext context; protected InitialContext initialContext;
protected String utName; protected String userTransactionName;
/**
* Configure this transaction factory. Specifically here we are attempting to
* resolve both an {@link #getInitialContext InitialContext} as well as the
* {@link #getUserTransactionName() JNDI namespace} for the {@link UserTransaction}.
*
* @param props The configuration properties
*
* @exception HibernateException
*/
public void configure(Properties props) throws HibernateException { public void configure(Properties props) throws HibernateException {
this.initialContext = resolveInitialContext( props );
this.userTransactionName = resolveUserTransactionName( props );
log.trace( "Configured JTATransactionFactory to use [{}] for UserTransaction JDNI namespace", userTransactionName );
}
/**
* Given the lot of Hibernate configuration properties, resolve appropriate
* reference to JNDI {@link InitialContext}.
* <p/>
* In general, the properties in which we are interested here all begin with
* <tt>hibernate.jndi</tt>. Especially important depending on your
* environment are {@link Environment#JNDI_URL hibernate.jndi.url} and
* {@link Environment#JNDI_CLASS hibernate.jndi.class}
*
* @param properties The Hibernate config properties.
* @return The resolved InitialContext.
*/
protected final InitialContext resolveInitialContext(Properties properties) {
try { try {
context = NamingHelper.getInitialContext( props ); return NamingHelper.getInitialContext( properties );
} }
catch ( NamingException ne ) { catch ( NamingException ne ) {
log.error( "Could not obtain initial context", ne );
throw new HibernateException( "Could not obtain initial context", ne ); throw new HibernateException( "Could not obtain initial context", ne );
} }
}
utName = props.getProperty( Environment.USER_TRANSACTION ); /**
* Given the lot of Hibernate configuration properties, resolve appropriate
* JNDI namespace to use for {@link UserTransaction} resolution.
* <p/>
* We determine the namespace to use by<ol>
* <li>Any specified {@link Environment#USER_TRANSACTION jta.UserTransaction} config property</li>
* <li>If a {@link TransactionManagerLookup} was indicated, use its
* {@link TransactionManagerLookup#getUserTransactionName}</li>
* <li>finally, as a last resort, we use {@link #DEFAULT_USER_TRANSACTION_NAME}</li>
* </ol>
*
* @param properties The Hibernate config properties.
* @return The resolved {@link UserTransaction} namespace
*/
protected final String resolveUserTransactionName(Properties properties) {
String utName = properties.getProperty( Environment.USER_TRANSACTION );
if ( utName == null ) { if ( utName == null ) {
TransactionManagerLookup lookup = TransactionManagerLookupFactory.getTransactionManagerLookup( props ); TransactionManagerLookup lookup = TransactionManagerLookupFactory.getTransactionManagerLookup( properties );
if ( lookup != null ) { if ( lookup != null ) {
utName = lookup.getUserTransactionName(); utName = lookup.getUserTransactionName();
} }
} }
return utName == null ? DEFAULT_USER_TRANSACTION_NAME : utName;
}
if ( utName == null ) { /**
utName = DEFAULT_USER_TRANSACTION_NAME; * {@inheritDoc}
*/
public Transaction createTransaction(JDBCContext jdbcContext, Context transactionContext)
throws HibernateException {
UserTransaction ut = getUserTransaction();
return new JTATransaction( ut, jdbcContext, transactionContext );
}
/**
* Get the {@link UserTransaction} reference.
*
* @return The appropriate {@link UserTransaction} reference.
*/
protected UserTransaction getUserTransaction() {
log.trace( "Attempting to locate UserTransaction via JNDI [{}]", getUserTransactionName() );
try {
UserTransaction ut = ( UserTransaction ) getInitialContext().lookup( getUserTransactionName() );
if ( ut == null ) {
throw new TransactionException( "Naming service lookup for UserTransaction returned null [" + getUserTransactionName() +"]" );
}
log.trace( "Obtained UserTransaction" );
return ut;
}
catch ( NamingException ne ) {
throw new TransactionException( "Could not find UserTransaction in JNDI [" + getUserTransaction() + "]", ne );
} }
} }
public Transaction createTransaction(JDBCContext jdbcContext, Context transactionContext) /**
throws HibernateException { * Getter for property 'initialContext'.
return new JTATransaction( context, utName, jdbcContext, transactionContext ); *
* @return Value for property 'initialContext'.
*/
protected InitialContext getInitialContext() {
return initialContext;
} }
/**
* Getter for property 'userTransactionName'.
* The algorithm here is
*
* @return Value for property 'userTransactionName'.
*/
protected String getUserTransactionName() {
return userTransactionName;
}
/**
* {@inheritDoc}
*/
public ConnectionReleaseMode getDefaultReleaseMode() { public ConnectionReleaseMode getDefaultReleaseMode() {
return ConnectionReleaseMode.AFTER_STATEMENT; return ConnectionReleaseMode.AFTER_STATEMENT;
} }
/**
* {@inheritDoc}
*/
public boolean isTransactionManagerRequired() { public boolean isTransactionManagerRequired() {
return false; return false;
} }
/**
* {@inheritDoc}
*/
public boolean areCallbacksLocalToHibernateTransactions() { public boolean areCallbacksLocalToHibernateTransactions() {
return false; return false;
} }
/**
* {@inheritDoc}
*/
public boolean isTransactionInProgress( public boolean isTransactionInProgress(
JDBCContext jdbcContext, JDBCContext jdbcContext,
Context transactionContext, Context transactionContext,
@ -120,13 +240,8 @@ public class JTATransactionFactory implements TransactionFactory {
return JTAHelper.isInProgress( jdbcContext.getFactory().getTransactionManager().getStatus() ); return JTAHelper.isInProgress( jdbcContext.getFactory().getTransactionManager().getStatus() );
} }
else { else {
try { UserTransaction ut = getUserTransaction();
UserTransaction ut = ( UserTransaction ) context.lookup( utName ); return ut != null && JTAHelper.isInProgress( ut.getStatus() );
return ut != null && JTAHelper.isInProgress( ut.getStatus() );
}
catch ( NamingException ne ) {
throw new TransactionException( "Unable to locate UserTransaction to check status", ne );
}
} }
} }
catch ( SystemException se ) { catch ( SystemException se ) {