Have you ever wondered why there are six types of
Transaction demarcation attributes
(NotSupported, Required,
Supports, RequiresNew,
Mandatory, and Never) supported in beans
using
container-managed transactions (CMT), but if you're using
bean-managed transactions (BMT), the only functionality
the EJB spec provides is to begin and commit/roll back transactions
via the
UserTransaction interface? Obviously the CMT model seems
more capable--BMT lacks, for example, the ability to suspend and
resume the current transaction, which means that you can't emulate
the RequiresNew and NotSupported
demarcations in BMT beans, at least not when you're using just the
UserTransaction interface.
While the EJB specification doesn't explain why above-mentioned
asymmetry exists, there is still a legitimate way to suspend and
resume transactions in the BMT model. If you ever studied the
contents of the
javax.transaction package, you've probably noticed that
along with the UserTransaction interface there is a
TransactionManager interface that basically looks like
an "extended" UserTransaction: the same methods--begin(), commit(), and
rollback()--plus
suspend() and
resume().
If we can get a TransactionManager implementation
from within our EJB, we will be able to achieve our goal of
suspending and resuming transactions programmatically. Both the J2EE
1.3 and EJB 2.0 specifications are quiet about the availability of
TransactionManager, but neither one of them explicitly
prohibits using it. And since Container uses the Java Transaction API (JTA)
internally for CMT transaction demarcation, we can be almost 100 percent
sure that TransactionManager is present, and that it's
just a matter of obtaining a reference to it in your code.
|
Related Reading
Head First EJB |
In this article, we will see how to get a
TransactionManager in several popular containers and
show how to use it to extend functionality of bean-managed
transactions, making them as powerful as container-managed
transactions. We'll also outline some risks involved in using this
advanced functionality, and at the end, explore how
TransactionManager is used in the popular Spring framework.
TransactionManager in Various J2EE
ServersThe J2EE and EJB specifications don't describe any standard
means to obtain a reference to TransactionManager.
Every J2EE container vendor is free to place it anywhere, or even
to not provide any mechanism to access it from application code. In
practice, though, all modern containers have mechanisms to obtain
it. Below are examples how to get TransactionManagers
from most popular J2EE containers.
UserTransaction (WebLogic, Orion, OC4J)Any J2EE-compatible container must make the UserTransaction object available in JNDI under
java:comp/UserTransaction. Since the
UserTransaction interface is a subset of
TransactionManager, some J2EE container vendors choose
to provide a common implementation of both of them.
WebLogic 8, Orion 2, and
Oracle's
OC4J EJB3 preview are examples of such an approach. In these
containers, a reference to TransactionManager can be
obtained just by getting a UserTransaction object from
JNDI and casting it to TransactionManager. This is
probably the simplest case.
private TransactionManager getFromUserTransaction()
throws Exception {
InitialContext ctx = new InitialContext();
UserTransaction ut = (UserTransaction)
ctx.lookup("java:comp/UserTransaction");
if (ut instanceof TransactionManager) {
log("UserTransaction also TransactionManager");
return (TransactionManager)ut;
}
return null;
}
TransactionManager Directly from JNDI (JBoss,
WebLogic)In JBoss 3 and WebLogic 8,
TransactionManager is available in JNDI, albeit under
different names, and therefore can be obtained by simple
lookup:
private TransactionManager getFromJNDI()
throws Exception {
InitialContext ctx = new InitialContext();
try {
// WebLogic
return (TransactionManager)
ctx.lookup("javax.transaction.TransactionManager");
}
catch (Exception e) { }
try {
// JBoss
return (TransactionManager)
ctx.lookup("java:/TransactionManager");
}
catch (Exception e) { }
return null;
}
|
TransactionManager from a Custom Factory (WebSphere)In WebSphere
4/5/6, a reference to TransactionManager should be
obtained from a factory class. It is annoying, though, that the
factory class name has changed between different WebSphere
versions.
public TransactionManager getFromWebsphereFactory()
throws Exception {
try {
// WebSphere 5.1 or 6.0
return
com.ibm.ws.Transaction.TransactionManagerFactory
.getTransactionManager();
}
catch (ClassNotFoundException ex) {}
try {
// WebSphere 5.0
return
com.ibm.ejs.jts.jta.TransactionManagerFactory
.getTransactionManager();
}
catch (ClassNotFoundException ex) {}
try {
// WebSphere 4.0
com.ibm.ejs.jts.jta.JTSXA..getTransactionManager();
}
catch (ClassNotFoundException ex) { }
return null;
}
In WebLogic 7/8/9, a reference to
TransactionManager can be obtained from the static
method getTransactionManager(), defined in
TxHelper in WebLogic 7. This class was deprecated in
WebLogic 8 and
TransactionHelper should be used instead.
public TransactionManager getFromWebLogicFactory()
throws Exception {
try {
// WebLogic 8/9
return
weblogic.transaction.TransactionHelper
.getTransactionManager();
}
catch (ClassNotFoundException ex) {}
try {
// WebLogic 7
return
weblogic.transaction.TxHelper
.getTransactionManager();
}
catch (ClassNotFoundException ex) {}
return null;
}
TransactionManagerOnce you've successfully obtained a reference to
TransactionManager, you can use it for transaction
suspension and resumption, as shown in the sample code below:
...
// obtain UserTransaction object and start transaction
InitialContext ctx = new InitialContext();
UserTransaction userTransaction = (UserTransaction)
ctx.lookup("java:comp/UserTransaction");
// start first transaction
userTransaction.begin();
// obtain TransactionManager
// using one of the methods described above
TransactionManager tm = getTransactionManager();
// suspend transaction
// suspend() returns reference to suspended
// Transaction object which later should be passed
// to resume()
Transaction transaction = tm.suspend();
// here you can do something outside of transaction
// or start new transaction,
// do something and then commit or rollback
userTransaction.begin();
// commit subtransaction
userTransaction.commit();
// resume suspended transaction
tm.resume(transaction);
// commit first transaction
userTransaction.commit();
...
|
As you can see, with the help of the
TransactionManager interface, you can extend the
standard functionality provided by UserTransaction and
achieve in BMT code the same level of flexibility available for CMT
beans.
One non-obvious thing to remember is that when a transaction is
suspended, it doesn't mean that the transactional timer is stopped.
In other words, if the transaction timeout was set to 30 seconds
and then transaction was suspended for 20 seconds and resumed, the
transaction would have only ten seconds left to run before timeout.
Transaction suspension simply disassociates a transaction with the
running thread, and the resume() call associates it
back without affecting the transactional timeout timer.
Because the J2EE specification doesn't require the availability and
functionality of TransactionManager in J2EE containers
(although we know that it should be there because of underlying JTA
infrastructure), there are a few issues that exist in some
application servers. For example, one particularly strange anomaly
exists in WebLogic versions 7, 8, and 9 (beta): if a transaction
was marked for rollback (by calling
UserTransaction.setRollbackOnly()) and then suspended,
attempting to resume the suspended transaction will fail with:
javax.transaction.InvalidTransactionException: Attempt to
resume an inactive transaction
The following code illustrates this behavior.
...
// obtain UserTransaction object and start transaction
InitialContext ctx = new InitialContext();
UserTransaction userTransaction = (UserTransaction)
ctx.lookup("java:comp/UserTransaction");
userTransaction.begin();
// mark for rollback
userTransaction.setRollbackOnly();
TransactionManager tm = getTransactionManager();
// suspend transaction
Transaction transaction = tm.suspend();
// resume suspended transaction
// this call will fail with InvalidTransactionException
// in WebLogic
tm.resume(transaction);
...
Fortunately, there is a workaround for this particular problem.
WebLogic's implementation of TransactionManager, along
with the standard resume(Transaction transaction)
method, has a forceResume(Transaction transaction)
method that can be used instead. The code below demonstrates the
pattern that should be used when your code is running in WebLogic.
Note that in this case, the reference to
TransactionManager should be cast to WebLogic's custom
implementation interfaces (
weblogic.transaction.TransactionManager in WebLogic 7 or
weblogic.transaction.ClientTransactionManager in
WebLogic 8 and above).
...
// obtain UserTransaction object and start transaction
InitialContext ctx = new InitialContext();
UserTransaction userTransaction = (UserTransaction)
ctx.lookup("java:comp/UserTransaction");
userTransaction.begin();
// mark for rollback
userTransaction.setRollbackOnly();
TransactionManager tm = getTransactionManager();
// suspend transaction
Transaction transaction = tm.suspend();
// resume suspended transaction
try {
// first try standard JTA call
tm.resume(transaction);
}
catch (InvalidTransactionException e) {
// standard method failed, try forceResume()
if (tm instanceof
weblogic.transaction.ClientTransactionManager) {
// WebLogic 8 and above
((weblogic.transaction.ClientTransactionManager)tm)
.forceResume(transaction);
}
else if (tm instanceof
weblogic.transaction.TransactionManager) {
// WebLogic 7
((weblogic.transaction.TransactionManager)tm)
.forceResume(transaction)
}
else {
// cannot resume
throw e;
}
}
...
|
TransactionManager Usage in the Spring FrameworkThe techniques described above are used extensively in the
popular Spring
framework for transactional proxies. You should be aware that
every time you configure Spring's
TransactionProxyFactoryBean with PROPAGATION_REQUIRES_NEW or
PROPAGATION_NOT_SUPPORTED, transaction attributes, and
JtaTransactionManager, Spring uses JTA's
TransactionManager for transaction
suspension and resumption. Spring is smart enough--sometimes too
smart for my taste--to discover TransactionManager
in your container, even if you don't specify it. For example, you
can supply a JNDI name for UserTransaction when
defining JtaTransactionManager in a Spring application
context and it will "autodetect" TransactionManager if
UserTransaction also implements it; as shown above,
this is true for WebLogic, Orion, and Oracle OC4J.
Sometimes, this could be undesirable, especially if you want to
stay strictly within a J2EE/EJB specification and ensure complete
portability across all J2EE containers. Sometimes, as we've seen
above, there could be issues with programmatic transaction suspend
and resume, although Spring knows how to work around some of them,
at least the issue described above with marking transaction for
rollback before suspending in WebLogic. In this case, you can set
the autodetectTransactionManager property to
false when configuring
JtaTransactionManager. If you do so, any attempt to
use PROPAGATION_REQUIRES_NEW or
PROPAGATION_NOT_SUPPORTED transactional attributes
should fail with
TransactionSuspensionNotSupportedException, but
PROPAGATION_REQUIRED,
PROPAGATION_SUPPORTS,
PROPAGATION_MANDATORY, and
PROPAGATION_NEVER should work normally. This
corresponds to the functionality provided by the JTA
UserTransaction and is guaranteed to work in any J2EE-compatible container.
Below is the transaction manager definition in a Spring
application context with TransactionManager autodetect
disabled.
<bean id="transactionManager" class=
"org.springframework.transaction.jta.JtaTransactionManager">
<property name="userTransactionName">
<value>javax.transaction.UserTransaction</value>
</property>
<property name="autodetectTransactionManager">
<value>false</value>
</property>
</bean>
Support for the JTA TransactionManager interface is
not required by the J2EE specification, but since J2EE uses JTA as its
underlying transaction infrastructure, almost all J2EE servers
expose it as an extension to J2EE. There are some known issues with
compatibility, and under certain circumstances, container-specific
code can be used to work around it. Nevertheless, using
TransactionManager in J2EE is a powerful feature if
you need to implement programmatic transaction suspension. Just
always test it first in your chosen J2EE server.
Dmitri Maximovich is an independent consultant specializing in software design and development.
Return to ONJava.com.
Copyright © 2009 O'Reilly Media, Inc.