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
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
Basically a website can provide a Strict-Transport-Security response header to force the browser to use HTTPS automatically for all subsequent request for a duration of time.
The response header above says for the next 31536000 seconds (1 year), the browser has to use HTTPS to access this domain, and all its subdomains.
This becomes a gotcha if you’re testing a new subdomain over HTTP by updating your local host file, you might be wondering why your browser wouldn’t let you to connect using HTTP immediately — without consulting with the web server first.
Fortunately it can be overcame by simply clearing your browser cache.
It’s unimaginable how many apps still force its user to manually update — they’re very annoying! Fortunately if your app is a Java Web App (eg: running in Tomcat) you can set it up to auto-update silently. This is very helpful if you deploy your web-app onsite on clients’ infrastructure.
This article assumes you have a basic understanding of Spring MVC. The article is written against Spring Framework version 4.0.3.RELEASE.
The UpdaterService
The first thing to setup is an UpdaterService that checks periodically if a new version is available. You will need a TaskScheduler to schedule the periodic job. Feel free to either use Java annotation or XML style:
// Java annotation
g
public class AppConfig {
...
}
Once setup, you can decorate a method with annotation and it will be invoked by the scheduler
thread every specified period.
In below example my UpdaterService class has a checkAndInstallUpdate() method that get invoked by the scheduler thread every 8 hours.
public class UpdaterService {
(fixedDelay=8*3600)
public void checkAndInstallUpdate() {
}
}
I will not go into much detail on how to check and download the update. There are tons of way of doing it and one popular method is to use Apache HttpClient library.
Typically you would upload your war/jar artifact in your website and have a REST service for apps to check if new version is available. If so then download and install it.
The other thing you need to make sure is the war/jar file isn’t file-system-locked when the app is running so it can be overwritten by the new version. Self-executing Tomcat does not lock the jar.
Restarting Tomcat
Here comes the tricky bit. First use ProcessBuilder to spawn a new OS process
In the example above it is assumed you have a windows script called run.bat and the value of environment variable MY_APP_PATH is set to the folder that contain the run script.
And directly after that, terminate the JVM using System.exit(0)
System.exit(0);
The termination of the old process will not affect the new ones. And yes, you need to start the new process before you terminate the old one, otherwise it wouldn’t have chance to do so.