TransactionTemplate and Rollback Rules

By Kenan Sevindik

When I was playing with TransactionTemplate during one of my Spring training sessions, I promptly suggested the audience try changing the default rollback rules while using TransactionTemplate. After all, TransactionTemplate encapsulates boilerplate transaction begin, commit/rollback statements, and we only give the business logic part, which it executes inside that begin…commit/rollback block. Because of this, I’ve thought so far that TransactionTemplate’s behavior would be the same as declarative transaction management’s. In other words, it would rollback if the exception is unchecked, otherwise commit if the exception is checked, and it would be possible to change this default behavior. Therefore, I implemented a code very similar to the following and expected to see commit even though the code throws RuntimeException.

RuleBasedTransactionAttribute rules = new RuleBasedTransactionAttribute();
rules.getRollbackRules().add(new NoRollbackRuleAttribute(RuntimeException.class));

TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager,rules);

transactionTemplate.execute(new TransactionCallbackWithoutResult() {

    @Override
    protected void doInTransactionWithoutResult(TransactionStatus status) {

        if(true) throw new RuntimeException();

    }
});

Unfortunately, I was plain wrong! TransactionTemplate continued to rollback without taking my custom no-rollback rule defnition into account. I was surprised and looked into TransactionTemplate’s source code, and saw following code block;

public  T execute(TransactionCallback action) throws TransactionException {
    if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
        return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
    }
    else {
        TransactionStatus status = this.transactionManager.getTransaction(this);
        T result;
        try {
            result = action.doInTransaction(status);
        } catch (RuntimeException ex) {
            // Transactional code threw application exception -> rollback
            rollbackOnException(status, ex);
            throw ex;
        } catch (Error err) {
            // Transactional code threw error -> rollback
            rollbackOnException(status, err);
            throw err;
        } catch (Exception ex) {
            // Transactional code threw unexpected exception -> rollback
            rollbackOnException(status, ex);
            throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
        }
        this.transactionManager.commit(status);
        return result;
    }
}

TransactionTemplate rollbacks on any exception without consulting RuleBasedTransactionAttribute at all. It was strange because, after all, it was expecting TransactionDefinition as a constructor argument, and I thought that it would be possible to give custom rollback rules instead of the default one, even though TransactionTemplate extends from DefaultTransactionDefinition. If you look at that constructor closely, you will notice that all parameters of the TransactionDefinition input argument are copied to TransactionTemplate except rollback rules. Therefore, they are simply ignored!

In summary, it appears that Spring guys probably assumed that it would be meaningless to deal with rollback rules if someone does programmatic transaction management. The programmer would decide on whether to commit or rollback on any exception after all. This would be meaningful if one uses PlatformTransactionManager for programmatic transaction management, but TransactionTemplate is a bit different in my point of view. It hides common and repeating boilerplate code, and it would be better if this code had taken rollback rules into account when an exception occurred.

Share: X (Twitter) LinkedIn