Why are the transactions rolled back even when propagation=Propagation.REQUIRES_NEW in second method in Spring service class?
Asked Answered
L

4

6

Basic settings are all fine now and I started to try transactions. Struts+Spring+Hibernate annotation transaction manager. This is the sample code in Action, will call a service class:

    userService.addUser();

Here is the addUser() method in service class:

    @Transactional(value="deu"  )
    public void addUser() {     
        userDao.addUser();
        this.addUser2();

    }

First, I called addUser in userDao, which will insert a user. Second, I called addUser2 in another method in this service class.

    @Transactional(value="deu" , propagation=Propagation.REQUIRES_NEW  )
    public void addUser2()   {      
    //should be a new transaction and will not affect the previous one. 
            //this one will fail but should not affect the previous one.
        userDao.addUserFail();        
    }

And this one will failed due to null PK. I suppose the second call (addUser2) will fail but will not affect the previous one. However, the user is not inserted.

If I only call:

   @Transactional(value="deu"  )
    public void addUser() {     
        userDao.addUser();
        //this.addUser2();      
    }

It is working, meaning basic settings like database is not wrong.

Any idea?

Lied answered 9/3, 2013 at 17:13 Comment(0)
R
7

Spring's declarative transaction handling works using AOP proxies. When you get a tranasactional bean, you in fact get a proxy which wraps your bean instance, intercepts the method call, starts a transaction if necessary, then calls the actual bean's method, then commits or rollbacks the transaction if necessary.

But you're calling a method of your bean from another method inside the same bean, so the proxy is bypassed, and can't apply any transactional behavior.

Put the method in another bean, or use AspectJ, which instruments the byte code and can intercept intra-bean method calls.

For a more detailed explanation, see the Spring documentation.

Rivard answered 9/3, 2013 at 17:23 Comment(4)
Hi JB: it is still not working. I injected another service (countryService) in userService, and in addUser method, I called dao.addUser (this one will insert a user ) , and then countryService.addCountry ( this one is propagation=Propagation.REQUIRES_NEW and will fail due to null PK). However, the first user added will be roll back also...I don't know why now~Lied
Hi JB Nizet: I fould another article you wrote about requires_new, I guess I misunderstand something in Spring. Can I ask again, if inner transaction is rollback and throws its exception to outer transaction, does outer transaction also rollback too?(if outer does not do anything) If so, if inner is changed REQUIRED and this time throws a Exception again and outer catch it don't do anything, does this two situation means same thing? If this is true, then what I declared is useless...it is only depends on my program logic! :(Lied
I wrote down my found in the answer, I hope it is right and please have a look again, thanks!Lied
You got it right. If the second service uses REQUIRED, both services are in the same transaction, and the transaction will be marked for rollback if an exception is thrown by the second (or first) service. Catching the exception in the first service won't change anything: the transaction is marked for rollback already. If the second service uses REQUIRES_NEW and throws an exception, it has its own transaction, which will be rollbacked. But the exception, as any other exception, will propagate to the caller (the first service). So if it doesn't catch it, its transaction will also be rollbacked.Rivard
L
2

I did some test and found the conclusion.

  1. If 2nd service(inner) is REQUIRED and throw Exception, even 1st transaction catch it, both of them will rollback ( Because they are in same boat !)

  2. If 2nd service(inner) is REQUIRES_NEW and throw Exception, outer has to deal with this rollback (It is NOT my rollback, but I have to do something), if not catch it , this exception in outer will trigger outer to rollback (even it is not his Exception, but it is an Exception ! ). So outer has to do something for this situation (Set rollback for or catch it ).

Is it right ?

Lied answered 10/3, 2013 at 5:23 Comment(1)
Yes, the 2nd method (REQUIRES_NEW) is surrounded by Spring transaction handling which will throw a new UnexpectedRollbackException to indicate that the new transaction failed. You need to try-catch that in your main method, to avoid "leaking this runtime exception outside your method boundaries" <- which would trigger rollback for your main transaction too.Dominations
V
0

It is because of Spring AOP architecture.

Spring AOP uses proxies, so aspects are just executed when calling methods on the proxy.

When calling this.addUser2(...) you are calling the method on the self object, not the proxy. So no aspect is executed, and no TX management is done.

You can do following things:

  • Move the addUser2 method to another bean, (for example UserService2), and then inject the new bean into UserService and call that method using the userService2.addUser2().
  • Inject UserService into UserService (I'm not sure if it can be done), and call the addUser2() by using userService.addUser2() not this.addUser2().
Vintager answered 9/3, 2013 at 17:24 Comment(1)
Your solution should be working, however, another serviced which is declared as Propagation.REQUIRES_NEW ( already injected now ) roll back my inserted user in first call.Lied
K
0

Just as Amir Pashazadeh said that he didn't knew how to call the proxy with a transactional context in the same bean here is an example:

@Component
public class UserService(){     

    @Autowired @Setter private ApplicationContext  applicationContext;
    @Autowired @Setter private UserDao             userDao;

    @Transactional(value="deu"  )
    public void addUser() {     

        userDao.addUser();
        try{
           getProxy().addUser2();
        catch(Exception ex){
           // Avoid rolling back main transaction
           log("OMG it failed!!")
        }
    }

    @Transactional(value="deu" , propagation=Propagation.REQUIRES_NEW  )
    public void addUser2()   {      
    //should be a new transaction and will not affect the previous one. 
            //this one will fail but should not affect the previous one.
        userDao.addUserFail();        
    }

    private UserService getProxy() {
        return applicationContext.getBean(UserService.class); 
    }
}

Beware that Spring seems to throw an UnexpectedRollbackException if you catched the exception in addUser2 but the transaction was already marked for Rollback.

Kantian answered 6/8, 2015 at 11:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.