2

I have a dao class (MyDao) which is marked with @Transactional annotaion (on class level) with no additional parameters. In this dao class I have a method which in some case needs to throw a checked exception and perform a transaction rollback. Something like this:

@Transactional
public class MyDao {

    public void daoMethod() throws MyCheckedException() {

        if (somethingWrong) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            throw new MyCheckedException("something wrong");
        }
}

This works perfectly fine. However, this dao method is called from a service method, which is also marked as @Transactional:

public class MyService {

    @Autowired
    private MyDao myDao;

    @Transactional
    public void serviceMethod() throws MyCheckedException {
        myDao.daoMethod();
    }

}

The problem is that when daoMethod() is called from serviceMethod() and marks the transaction as rollback only, I get an UnexpectedRollbackException.

Under the hood, Spring creates two transactional interceptors: one for MyDao and one for MyService. When daoMethod() marks the transaction for rollback, the interceptor for MyDao performs the rollback and returns. But then the stack moves to the interceptor for MyService, which finds out about the previous rollback, and throws the UnexpectedRollbackException.

One solution would be to remove the @Transactional annotation from MyDao. But this is not feasible right now because MyDao is used at a lot of places and this could cause errors.

Another solution would be to not set the transaction as rollback only in daoMethod() but rather mark serviceMethod() to revert the transaction on MyCheckedException. But I don't like this solution because I have a lot of these "service methods" and I would have to mark all of them explicitly to rollback on that exception. Also everyone who would add a new service method in the future would have to think of this and therefore it creates opportunities for errors.

Is there a way I could keep setting the transaction as rollback only from daoMethod() and prevent Spring from throwing the UnexpectedRollbackException? For instance, with some combination of parameters isolation and propagation?

Jardo
  • 1,939
  • 2
  • 25
  • 45

2 Answers2

1

I figured it out. I have to explicitly tell also the "outer" interceptor, that I want to rollback the transaction. In other words, both interceptors need to be "informed" about the rollback. This means either catching MyCheckedException in serviceMethod() and setting the transaction status to rollback only, or to mark serviceMethod() like this @Transactional(rollbackFor=MyCheckedException.class).

But as I mentioned in the OP, I want to avoid this because it's prone to errors. Another way is to make @Transactional rollback on MyCheckedException by default. But that's a completely different story.

Community
  • 1
  • 1
Jardo
  • 1,939
  • 2
  • 25
  • 45
0

Throwing an exception inside the transaction already trigger a rollback, using setRollbackOnly() is redundant here and that's probably why you have that error.

If the Transactionnal on the DAO is set up with Propagation.REQUIRED which is the default, then it will reuse an existing transaction if there is already one or create one if there is none. Here it should reuse the transaction created at the service layer.

  • Spring only rollbacks transaction on runtime exceptions by default. This can be modified by attribute `rollbackFor` on the @Transactional annotation. But when I do it on MyDao, I get the same result as setting the transaction status. – Jardo Nov 08 '16 at 13:45
  • You should use the framework configuration to rollback on checked exception instead of changing the transaction status manually, you can set rollbackFor on Exception.class to rollback on all checked exceptions. And you can put this on all your services (or create an annotation with that configuration that you can use instead). – Nyamiou The Galeanthrope Nov 08 '16 at 13:52
  • You can also put the checked exception inside a runtime exception and throw it instead. I don't know your use case here, but it could be a solution. – Nyamiou The Galeanthrope Nov 08 '16 at 13:55
  • My problem isn't "How to rollback the transaction". My problem is that when I have two "nested" interceptors, the inner interceptor performs the rollback and later the outer interceptor throws UnexpectedRollbackException. The way I tell Spring to perform the rollback doesn't matter. – Jardo Nov 08 '16 at 13:59