Thursday, December 21, 2006

Through the looking glass

(Congratulations to me, the 1,000,000th person to use this title in this context)

Project Looking Glass, Sun's Java 3D desktop (Vista Killer) has finally released 1.0.0, and of course I had to check it out. Here's a screenshot:

Wednesday, December 20, 2006

Transactions and exceptions revisited

As Jonas (Hello, world!) embarrasingly enough pointed out, the default Spring transaction semantics with respect to exceptions are identical with EJB's (you can't escape EJB!). So that's why Spring doesn't roll back on checked exceptions, but the question remains: why would anyone ever not want to roll back a transaction when an exception is thrown, checked or unchecked?

We had a long discussion which I'm going to try and sum up here. Obviously you (almost) never want an exception to leave the transactional layer without rolling back the transaction, since that means that something went wrong, but the transaction commited anyway. Suppose you have something like this:

@Transactional
public void registerNewUser(User user) throws LDAPException {
log.info("Registering user: " + user);
userDao.insertUser(user);
ldapService.registerEntry(user.getFirstName(), user.getLastName()); // Let's assume this throws a (checked) LDAPException
}

in the service layer, and then you call this method from outside the service layer, for example in an MVC controller. Now if the call to create the LDAP entry fails (throws a checked exception), the user data is stored even though an exception is thrown out to the calling controller, which can't do anything about it.

This scenario could be handled in two different ways, depending on business requirements: either a failed LDAP registration is fatal, and we need to roll back the transaction to avoid storing user data, or an LDAP exception is not fatal and we should catch and gracefully handle the failure inside the registerNewUser() service method.

If we decide to roll back, we have a few options:
So far it seems that simplifying the rollback rules to "always roll back on an Exception, I don't care if it's checked" is a good idea. But if it's the case that an LDAPException isn't fatal after all, the situation becomes a little more complicated. We modify the method like this:

@Transactional
public void registerNewUser(User user) {
log.info("Registering user: " + user);
userDao.insertUser(user);
try {
ldapService.registerEntry(user.getFirstName(), user.getLastName()); // Let's assume this throws a (checked) LDAPException
} catch (LDAPException e) {
// Log and move on
log.error("LDAP registration failed: " + e.getMessage());
}
}

No checked exception is thrown anymore. Furthermore, assume that the LDAP service call is also transactional, and that we're using the default propagation behaviour, REQUIRED, so that the LDAP service call participates in the same transaction as the registerNewUser() started. If the rule is to always roll back on any transaction, the exception thrown in the LDAP service call will mark the current transaction for rollback, even though we catch it in the user service method! That means that we have lost the ability to pass on failure messages between horizontal transactional calls (service-to-service).

So to conclude:
  1. We need to keep a way to pass on failure messages between calls inside the same transaction, and checked exceptions are a good candidate for that (certainly better than returning a negative integer).
  2. You should not expose service methods that throw checked exceptions to layers above/outside the transactional layer.

Thursday, December 14, 2006

This you need to know

Section 9.5.3 in Spring's reference documentation explains how and when transactions are rolled back, and also states the default behaviour with respect to rolling back transactions. The behaviour might not be what you expect (quoted):

However, please note that the Spring Framework's transaction infrastructure code will, by default, only mark a transaction for rollback in the case of runtime, unchecked exceptions; that is, when the thrown exception an instance or subclass of RuntimeException. (Errors will also - by default - result in a rollback.) Checked exceptions that are thrown from a transactional method will not result in the transaction being rolled back.

Note the very last sentence (my bold). Checked exceptions do not trigger a transaction rollback!

There is of course a way change this behaviour by listing an array of Exception classes for which to roll back for, the details are in section 9.5.3 and below.

Personally I find this quite counter-intuitive, but I suppose there is good reason for this default behaviour. In either case, it's very important to be aware of how it works.

Wednesday, December 13, 2006

Faster DAO testing

If you're prepared to step away just a little bit from how the production environment is set up, here's one way to speed up Hibernate DAO testing considerably:

First of all, don't add a (Annotation)LocalSessionFactoryBean to your test context, just a DataSource and a hibernate.cfg.xml, and don't add any mapped/annotated classes to the configuration file either. Then create a base DAO test class that looks something like this:

public class AbstractDaoTest {

SessionFactory sessionFactory;

public AbstractDaoTest(Class... classes)
{
// Read hibernate.cfg.xml from root of classpath, for example src/test/resources
Configuration configuration = new Configuration();
for (Class clazz : classes) {
configuration.addClass(clazz);
}
sessionFactory = configuration.buildSessionFactory();
}

}

Then place all your DAOs and the DataSource in the test application context, and start writing your DAO tests like this:

public class ParentDaoTest extends AbstractDaoTest {

ParentDao dao;

public ParentDaoTest() {
// Parent has a relation to the Child class, so we need two classes mapped
super(Parent.class, Child.class);
}

public void setParentDao(ParentDao dao)
this.dao = dao;
}

public void testSomething() {
// Stuff that involves Parent and Child
}

}

Now there's only two classes that Hibernate needs to parse metadata for and create CRUD SQL for and so on, which can speed up application context startup considerably compared to mapping all domain classes, if you have 10-20 classes in you domain model.

Sunday, December 10, 2006

YES

He's on his way!

Go Christer!

Thursday, December 07, 2006

En vän har gått ur tiden

Min katt Morrissey dog idag. Han hade en medfödd, obotlig njursjukdom som till slut blev för svår att uthärda, och han somnade in på sin lurviga fäll hemma i soffan efter en överdos sömnmedel.


Tack Yvonne på Djurdoktorn för all hjälp.



Lillmosse, 2002-07-04 - 2006-12-07