Thursday, February 14, 2013

@Transactional mud

More wading through mud today at work, @Transactional mud on this occasion. The @Transactional annotation is Spring's way of specifying a method needs transactional semantics. It sounds simple enough: all you need is <tx:annotation-driven/> in your application context to tell Spring you'll be using annotations to demarcate transactions, and a few of those @Transactional annotations sprinkled around your code, like so:
@Service
@Transactional(rollbackFor = Throwable.class, readOnly = true)
public class MyService {

  @Transactional(readOnly = false)
  public void doStuff() throws SomeCheckedException {
    // ...
  }
}
Following the principle of least astonishment, I assumed the code above defined the doStuff() method as running in a read-write transaction (readOnly = false) which rolls back for all types of exceptions (rollbackFor = Throwable.class). The cool thing here is that you can use a class level annotation to setup useful defaults while the method level annotation adjusts settings as required for a particular method.

It took quite a bit of head scratching to realize this is not actually true! With the above code, no rollback would occur if doStuff() threw SomeCheckedException! The reason is that the transaction attributes in the method level annotation replace those in the class level annotation rather that the two being merged together. If you think about it this makes sense: Spring has no way of knowing at run-time whether you explicitly specified the rollbackFor on the annotation or just used the default value! Since the default rollback behavior is to not rollback on checked exceptions, this can give quite surprising results.

Lesson learned: check!