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..
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
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.
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
Thanks, nice post