Spring 1.2’s Java 5 Based Transaction Annotations
Spring has from the beginning provided a powerful transaction abstraction. This abstraction provides several key benefits:
- Allows you to specify transaction demaraction policies for your code, either declaratively or programatically.
- Abstracts away the low-level transaction manager with a common transaction management abstraction, allowing for you to change TX strategies (for example, JTA (global) vs. local transactions) without impacting application code (or even your demaraction policies)
This abstraction is available while running in an AppServer, or as a standalone application.
While the programmatic API is easy to use, most people have preferred to use a declarative approach instead, since it means POJO business services can be wrapped without any code in those objects being aware of transactions at all. In the past, this has usually meant XML-based definitions of what objects and what methods in those objects should be wrapped, and with what transaciton semantics. The XML definitions are convenient to use, work in any environment, and a number of techniques exist for making them pretty compact.
However if you are willing to commit to a dependency on Java 5+ (JDK 1.5+), you will almost certainly want to consider using Spring’s support for transaction Annotations in JDK standard format, as the transaction attribute source. These Annotations were officially introduced in Spring 1.2, although they’d been available in essentially finished form in Spring’s CVS repository since late last summer.
Declaring transaction semantics directly in the Java source code puts the declarations much closer to the affected code, and there is generally not much danger of undue coupling, since typically, code that is deployed as transactional is always deployed that way. Additionally, all major IDEs now have support for Annotations, so using them is as easy as writing any Java code.
In the rest of this blog entry I’m going to describe the new Annotations. Note that some of this text is based upon similar content I wrote for the Spring documentation.
The Transactional Annotation
The org.springframework.transaction.annotation.Transactional Annotation is used to indicate that an interface, interface method, class, or class method should have transaction semantics.
@Transactional
public interface OrderService {
void createOrder(Order order);
List queryByCriteria(Order criteria);
...
Used in bare form, this Annotation specifies that an interface, class, or method must be transactional. Default transaction semantics are read/write, PROPAGATION_REQUIRED, ISOLATION_DEFAULT, TIMEOUT_DEFAULT, with rollback on a RuntimeException, but not Exception.
Optional properties of the annotation modify transaction settings.
Properties of the Transactional Annotation
| Property | Type | Description |
|---|---|---|
| propagation | enum: Propagation | optional propagation setting (defaults to PROPAGATION_REQUIRED) |
| isolation | enum: Isolation | optional isolation level (defaults to ISOLATION_DEFAULT) |
| readOnly | boolean | read/write vs. read-only transaction (defaults to false, or read/write) |
| rollbackFor | array of Class objects, must be derived from Throwable | optional array of exception classes which should cause rollback. By default, checked exceptions do not roll back, unchecked (RuntimeException derived) roll back |
| rollbackForClassname | array of String class names. | Classes must be derived from Throwable optional array of names of exception classes which should cause rollback |
| noRollbackFor | array of Class objects, must be derived from Throwable | optional array of exception classes which should not cause rollback. |
| noRollbackForClassname | array of String class names, must be derived from Throwable | optional array of names of exception classes which should not rollback |
The annotation may be placed before an interface definition, a method on an interface, a class definition, or a method on a class. It may exist on both an element of an interface, and a class which implements that interface. The most derived location takes precedence when evaluating the transaction semantics of a method.
Transactional annotation examples
In the following definition, we’re specifying that createOrder() should be transactional with default semantics:
public class OrderServiceImpl implements OrderService {
@Transactional
void createOrder(Order order);
public List queryByCriteria(Order criteria);
}
In the following example, the interface is annotated for read-only transactions, which will thus be the setting used for methods by default. The Annotation on the createOrder method overrides this, setting the transaction to read/write, and specifying that transactions should also (in addition to the defualt rollback rule for RuntimeException) rollback when the DuplicateOrderIdException (presumably a non-checked Exception) is thrown.
@Transactional(readOnly=true)
interface TestService {
@Transactional(readOnly=false,
rollbackFor=DuplicateOrderIdException.class)
void createOrder(Order order) throws DuplicateOrderIdException ;
List queryByCriteria(Order criteria);
}
Note that a class definition which implements this interface may still override these settings on its own class or method elements
.
Telling Spring to apply the Transactional annotation
By itself, adding instances of this Annotation to interface or class elements will not result in transactional wrapping of the implementation clases. Spring must still be told somehow to create transactional proxies around classes with these annotations.
The key is to take advantage of the org.springframework.transaction.annotation.AnnotationTransactionAttributeSource class, which is a Spring TransactionAttributeSource implementation which looks in class files for the Annotations we’ve been looking at, and from them produces standard Spring code>TransactionAttributes objects used by the transaction infrastructure.
Most Spring users doing transactional wrapping have been using TransactionProxyFactoryBean for the purpose, as detailed explicitly in the Spring reference manual. Let’s look at how to use TransactionAttributeSource with TransactionProxyFactoryBean. First, here’s a pure XML (and long-form) variant of a proxy definition:
<bean id="petStore" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager" ref="txManager"/> <property name="target" ref="petStoreTarget"/> <property name="transactionAttributes"> <props> <prop key="insert*">PROPAGATION_REQUIRED,-MyCheckedException</prop> <prop key="update*">PROPAGATION_REQUIRED</prop> <prop key="*">PROPAGATION_REQUIRED,readOnly</prop> </props> </property> </bean>
To convert to using Annotations, the TransactionAttributes property of the proxy factory bean, which specified transaction attributes in text form is replaced by the direct usage of the TransactionAttributeSource property, specifying an AnnotationTransactionAttributeSource.
<bean id="petStore" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager" ref="txManager"/> <property name="target" ref="petStoreTarget"/> <property name="transactionAttributeSource"> <bean class="...transaction.annotation.AnnotationTransactionAttributeSource"/> </property> </bean>
Since the TransactionAttributeSource property does not need to change at all for each proxy instance, when using parent and child bean definitions to avoid code duplication, the property may just be set on the base, parent definition and forgotten, there is never a need to override it in the child since the attribute source will read the right settings from each class file.
Using AOP to ensure the Transactional annotation is applied
The previous example is still more work than would be ideal. There is in principle no need for XML for each proxy (to point to the target bean) when the annotations in the class files themselves can be used as an indication that a proxy needs to be created for the annotated classes.
A more AOP focused approach allows a small amount of boilerplate XML (used once only, not for each target bean) to automatically ensure that all classes managed by Spring, which contain Transactional annotations in them are automatically wrapped transactionally. Spring AOP is detailed in the reference manual, as well as a number of books, although the reality is that there is actually no need to understand how the following code actually works. If you are interested though, the key is the use of DefaultAdvisorAutoProxyCreator, a BeanPostProcessor. Because it is a bean post processor, it gets a chance to look at every bean that is created as it is created. If the bean contains the Transactional annotation, a transactional proxy is automatically created to wrap it.
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> <bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor"> <property name="transactionInterceptor" ref="transactionInterceptor"/> </bean> <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"> <property name="transactionManager" ref="txManager"/> <property name="transactionAttributeSource"> <bean class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"/> </property> </bean>
A number of classes are involved here:
TransactionInterceptor: the AOP Advice, actually intercepts method call and wraps it with a transactionTransactionAttributeSourceAdvisor: AOP Advisor (holds the TransactionInterceptor, which is the advice, and a pointcut (where to apply the advice), in the form of a TransactionAttributeSource)AnnotationTransactionAttributeSource: TransactionAttributeSource implementation which provides transaction attributes read from class filesDefaultAdvisorAutoProxyCreator: looks for Advisors in the context, and automatically creates proxy objects which are the transactional wrappers
I hope that I’ve gotten you at least a little bit eager to try using the transaction annotations in your Spring-based applications. While annotations have the potential for misuse, and there’s already evidence of some of that happening, transaction annotations make a decent amount of sense in many situations. My feeling is that as more and more code starts running in a Java 5+ environment, and Java 5+ dependencies become acceptable, using annotations for transaction demarcation will probably be the prefered mechanism. They’re certainly a great way to reduce boilerplate XML, and move the information about what needs to be wrapped transactionally much closer to the actual code being wrapped, without any significant negatives.


Modified
