Transaction management is a subject that is generally left to the tender care of a senior developer (or architect). Given the messages coming from soem actors of the JavaEE community that with newer versions of JavaEE you don’t need Spring anymore, I was interested in some fact-checking on how transaction management was handled in both technologies.
Note: these messages were already sent one year and a half ago and prompted me to write this article.
Transaction demarcation
Note that although both technologies provide programmatic transaction demarcation (start transaction then commit/rollback), we’ll focus on declarative demarcation since they are easier to use in real life.
In EJB3, transactions are delimited by the @TransactionAttribute
annotation.
The annotation can be set on the class, in which case every method will have the transactional attribute, or per method.
Annotation at the method level can override the annotation at the class level.
Annotations are found automatically by the JavaEE-compliant application server.
In Spring, the former annotation is replaced by the proprietary @Transactional
annotation.
The behaviour is exactly the same (annotation at method/class level and possibility of overriding).
Annotations can only be found when the Spring bean definition file contains the http://www.springframework.org/schema/tx
namespace as well as the following snippet:
<tx:annotation-driven />
Alternatively, both technologies provide an orthogonal way to set transaction demarcation: in EJB3, one can use the EJB JAR deployment descriptor (ejb-jar.xml) while in Spring, any Spring configuration file will do.
Transactions propagation
In EJB3, there are exactly 6 possible propagation values: MANDATORY
, REQUIRED
(default), REQUIRES_NEW
, SUPPORTS
, NOT_SUPPORTED
and NEVER
.
Spring adds support for NESTED
(see below).
Nested transactions
Nested transactions are transactions that are started then commited/rollbacked during the execution of the root transaction. Nested transaction results are limited to the scope of this transaction only (it has no effect on the umbrella transaction).
Nested transactions are not allowed in EJB3; they are in Spring.
Read-only transaction
Read-only transactions are best used with certain databases or ORM frameworks like Hibernate. In the latter case, Hibernate optimizes sessions so that they never flush (i.e. never push changes from the cache to the underlying database).
I haven’t found a way (yet) to qualify a transaction as read-only in EJB3 (help welcome).
In Spring, @Transactional
has a readOnly
parameter to implement read-only transactions:
@Transactional(readOnly = true)
Local method calls
In local method calls, a bean’s method A()
calls another method B()
on the same instance.
The expected behavior should be that transaction attributes of method B()
is taken into account.
It’s not the case in neither technologies, but both offer some workaround.
In EJB3, you have to inject an instance of the same class and call the method on this instance in order to use transaction attributes. For example:
@Stateless
public MyServiceBean implements MyServiceLocal {
@EJB
private MyServiceLocal service;
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public void A() {
service.B();
}
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void B() {
...
}
}
In Spring, by default, transaction management is handled through pure Java proxies. If you want to have transaction management in local method calls, you’ll have to turn on AspectJ. This is easily done in the Spring beans definition file (but beware the side-effects, see the Spring documentation for more details):
<tx:annotation-driven mode="aspectj" />
Exception handling and rollback
Exception handling is the area with the greater differences between Spring and EJB3.
In EJB3, only runtime exceptions thrown from a method rollback a transaction delimited around this method by default.
In order to mimic this behavior for checked exceptions, you have to annotate the exception class with @ApplicationException(rollback = true)
.
Likewise, if you wish to discard this behavior for runtime exceptions, annotate your exception class with @ApplicationException(rollback = false)
.
This has the disadvantage of not being able to use the same exception class to rollback in a method and to still to commit despite the exception in another method. In order to achieve this, you have to manage your transaction programmatically:
@Stateless
public MyServiceBean implements MyServiceLocal {
@Resource
private SessionContext context;
public void A() {
try {
...
} catch (MyException e) {
context.setRollbackOnly();
}
}
}
In Spring, runtime exceptions also cause transaction rollback.
In order to change this behavior, use the rollbackFor
or noRollbackFor
attributes of @Transactional:
public MyServiceImpl {
@Resource
private SessionContext context;
@Transactional(rollbackFor = MyException.class)
public void A() {
...
}
}
Conclusion
There’s no denying that JavaEE has made giant steps in the right direction with its 6th version. And yet, small details keep pointing me toward Spring. If you know only about one - Spring or JavaEE 6, I encourage you to try "the other" and see for yourself which one you’re more comfortable with.