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:
- Catch LDAPException and throw a RuntimeException
- Access the current transaction and call setRollbackOnly (for example via TransactionAspectSupport.currentTransactionStatus().setRollbackOnly())
- Modify the transaction semantics in the configuration, by setting rollbackFor=LDAPException on the annotation
@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:
- 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).
- You should not expose service methods that throw checked exceptions to layers above/outside the transactional layer.
1 comment:
Is there any way by which we can specify transactions shouldn't be rolled back on certain runtime exceptions
Post a Comment