Using transaction from Spring
With Spring, controlling where a transaction is started can be as simple as adding the @Transactional annotation (and one extra line in the Spring configuration). The @Transactional is typically placed on the class that implements the interface of your service layer. For example you may have a MemberService:
public interface MemberService {
List findMembers(MemberCriteria criteria);
Member getMember(long id);
Member updateMember(Member member);
}
@Transactional
public class DefaultMemberService implements MemberService {
public List findMembers(MemberCriteria criteria) { ... }
public Member getMember(long id) { ... }
public Member updateMember(Member member) { ... }
}
Java transactions are bound to a thread (think ThreadLocal), the proxy can therefore join an existing transaction in the current call stack, and by default that is what happens. As a result you can safely call other transactional service methods from within your own transaction methods.
Introducing the CommandService
Here is a kind of weird service that I came up with a couple of years ago, named after the Command pattern, the CommandService:
@Transactional
public class DefaultCommandService implements CommandService {
public void inTransaction(Runnable command) {
command.run();
}
public <T> T inTransaction(Callable<T> command) {
try {
return command.call();
} catch (Exception e) {
throw new RuntimeException(
"CommandService#inTransaction("
+ command.toString() + ")", e);
}
}
}
As you can see it doesn't to anything except for executing the runnable or callable it is given. How can this be useful? Well, notice the @Transactional. Your block of code is now executed within a transaction!
Again, how can this be useful? Here are 3 use cases:
Use case 1: prevent fine grained service methods
Suppose you have a webpage that will update only one property of an entity, e.g. the email address of a member, and another page to update the password. Now to prevent concurrent update problems you want to get and update the member within the same transaction. You could make a new method on the service interface for each property you want to change. However, it is a good practice to keep interfaces concise. So instead we do something like this:
final long memberId = ...;
final String newEmail = ...;
Member freshMe =
commandService.inTransaction(new Callable<Member>() {
public Member call() throws Exception {
Member freshMe = memberService.getMember(memberId);
freshMe.setEmail(email.getNewEmail());
return memberService.updateMember(freshMe);
}
});
Use case 2: bundle operations for performance reasons
I once wrote some code that would keep thousands of LDAP records in sync with a relational database. In my test environment, a dodgy laptop, it would take 50 minutes to do the initial import of 15,000 records. Each record was persisted to the database in its own transaction.
After a small code change, I used the CommandService to persist the records in groups of 20. What do you think happened? The total time to import these 15000 records dropped from 50 to 2 minutes! Not bad, 25 times faster through 4 lines of extra code.
Careful positioning of transaction boundaries can have a dramatic effect on performance. CommandService can help you do that.
Use case 3: transactions in unusual places
Let us look at a little bit more detailed example of the PeriodicRetriever from my previous article (changes in italic).
public class CachingPostalCodeService {
private final Object postalCacheLock = new Object();
private List<PostalCode> postalCache;
private PeriodicExecutor postalCacheReloader;
private HibernatePostalCodeDao hibernatePostalCodeDao;
public CachingPostalCodeService() {
postalCacheReloader = new PeriodicExecutor(
TimeUnit.MINUTES.toMillis(10), new Runnable() {
public void run() {
refreshPostalCache();
}
public String toString() {
return "Postal code cache reloader";
}
});
}
public List<PostalCode> getPostalCodes() {
postalCacheReloader.requestStart();
synchronized (postalCacheLock) {
return postalCache;
}
}
private void refreshPostalCache() {
List<PostalCode> newPostalCodes =
Collections.unmodifiableList(
hibernatePostalCodeDao.getAll()
);
synchronized (postalCacheLock) {
postalCache = newPostalCodes;
}
}
// ... setter for hibernatePostalCodeDao ...
}
The first time getPostalCodes() is called, it is called through Spring's transaction proxy, so the method is executed within a transaction. So when the PeriodicRetriever is called, the runnable (which calls refreshPostalCache()) is immediately called within the same thread and thus also within the same transaction. No problem!
The second time getPostalCodes() is called (11 minutes later), PeriodicRetriever is called again but returns immediately. Meanwhile it starts a new thread which executes the runnable defined in CachingPostalCodeService's constructor. The important thing here is that the new thread does not join the transaction of its parent thread as transactions are bound to threads. The runnable then calls refreshPostalCache(). You may think you'll get a new transaction at that moment. However, a method call within the same class will never go through the transactional proxy. This is because the instance on which we call refreshPostalCache() is simply this, and not the proxy that was obtained from Spring. In other words: refreshPostalCache() is not executed within a transaction.
CommandService to the rescue (again, changes in italic):
public class CachingPostalCodeService {
private final Object postalCacheLock = new Object();
private List<PostalCode> postalCache;
private PeriodicExecutor postalCacheReloader;
private HibernatePostalCodeDao hibernatePostalCodeDao;
private CommandService commandService;
public CachingPostalCodeService() {
postalCacheReloader = new PeriodicExecutor(
TimeUnit.MINUTES.toMillis(10), new Runnable() {
public void run() {
commandService.inTransaction(new Runnable() {
public void run() {
refreshPostalCache();
}
});
}
public String toString() {
return "Postal code cache reloader";
}
});
}
// ... same as above ...
// ... setters for hibernatePostalCodeDao and commandService
}
Download
Feel free to use the complete version of CommandService in any way you see fit.
Conclusions
Watching your transaction boundaries can be very rewarding, both in code size as in performance. A command service, such as the one presented in this article can help you do that.