Spring Declarative Transaction Management With JDK Proxy vs AspectJ

What is this fancy words “Declarative Transaction Management” (DTM) ? Well.. basically it’s the “magic” behind the annotation in Spring.

By default Spring will use JDK Proxy for DTM, but it’s not without a big (yes BIG) gotchas.. Let’s take a look at below cliche example of transferring money from a bank account to another with slight variation — here several transfer requests are processed together by processBatch method:


public class AccountService {

public void processBatch(List xferReqs) {
    for(TransferReq xferReq : xferReqs) {
      transfer(xferReq.getAmount(), 
               xferReq.getFromAccountNo(),
               xferReq.getToAccountNo());
    }
  }


  public void transfer(double amount, long fromAccountNo, long toAccountNo) {
    checkBalanceIsGreaterThan(fromAccountNo, amount);
    withdraw(fromAccountNo, amount);
    deposit(toAccountNo, amount);
  }


  public void checkBalanceIsGreaterThan(long accountNo, double amount) {
    ...
  }


  public void withdraw(long fromAccountNo, double amount) {
    ...
  }


  public void deposit(long toAccountNo, double amount) {
    ...
  }

}

When another class invokes processBatch, you’d expect the database transaction to happen like this:

begin
req1 transfer
req1 checkBalanceIsGreaterThan
req1 withdraw
req1 deposit
commit

begin
req2 transfer
req2 checkBalanceIsGreaterThan
req2 withdraw
req2 deposit
commit
...

But it’s NOT! This is a big hidden pitfall that I’ve fallen into (had to learn the hard way right?). Here’s why..

In JDK Proxy Spring did the “magic trick” by automatically creating a subclass with extra DTM functionality (hence the name “proxy”) in place of the original transactional class. The subclass method actually do the transaction management before calling the underlying method.

This has the implication that transaction management will only occur if the method you invoke from a different class is a method.

Since in above example the processBatch method invoked is not a method, transaction management will not be performed!

Here’s a rough example of what the auto-created JDK proxy class might look like:

public class AccountServiceProxy extends AccountService {

public void processBatch(List xferReqs) {
    super(xferReqs); // <-- Superclass method is invoked here, 
                     //     the below transfer method will NOT execute
  }

public void transfer(double amount, long fromAccountNo, long toAccountNo) {
    // Code to "check if transaction exist, if not create one" goes here..

try {
  super(amount, fromAccountNo, toAccountNo);

  // Code to commit goes here..

} catch(Exception e) {
  // Code to rollback goes here..
}
} ... }

So yes your database transaction is a mess. Every single statement would be executed in its own transaction (oh no!)..

So How To Fix It?

Well you have several option. First is to keep using JDK Proxy but structure your class such that the first method invokes is a method. In the above example, the processBatch method can be moved into a different class AccountBatchProcessorService, hence when it invokes the proxy class’ transfer method DTM occurs.

Or you leave the code as it is and use the almighty AspectJ..

AspectJ Declarative Transaction Management

With AspectJ, DTM is not done by subclass “proxy”, but there’s a unicorn hiding beneath the cloud that adds DTM code to your method just before it’s compiled (sometimes it’s called “compile-time weaving”).

Here’s how you can enable AspectJ DTM for your project:

On the old-fashioned Spring xml configuration (don’t ask me the new-style annotation config yet, haven’t RTFM), to enable annotation you would have something like this:


  ...
  
  ...

You need to set it into aspectj mode

  

Then in your pom.xml, add dependency to spring-aspects

...

  org.springframework
  spring-aspects
  ${spring-version}

...

And configure aspectj-maven-plugin so that DTM mechanism is weaved when your code is compiled

...

  
    ...
    
      org.codehaus.mojo
      aspectj-maven-plugin
      1.6
      
        
          
            compile
            test-compile
          
          
            
              
                org.springframework
                spring-aspects
              
            
            ${java-version}
            ${java-version}
            ${java-version}
          
        
      
    
    ...
  

...

At this point your STS/eclipse project might throw error saying Maven configuration is not up to date. If so do right click on the project -> Maven -> Update project. If the setup is good in STS/eclipse you should see a half circle arrow on methods.
tx-arrow

Voila! Your bank transfer should work as expected now

Debugging Database Transaction

Lastly if you’re using Hibernate and log4j, you can get it to spit the SQL statements and ‘begin’ and ‘commit’ log by setting following loggers into DEBUG

log4j.logger.org.hibernate.engine.transaction.spi=DEBUG
log4j.logger.org.hibernate.SQL=DEBUG

Final Note

This article is written against following versions:

  • Spring 4.0.5.RELEASE
  • Hibernate 4.3.5.Final
  • aspectj-maven-plugin 1.6

Enjoy!

One thought on “Spring Declarative Transaction Management With JDK Proxy vs AspectJ”

Leave a Reply