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

吃可爱长大的小学妹 提交于 2019-12-06 02:26:07

问题


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?


回答1:


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.




回答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 ?




回答3:


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().



回答4:


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.



来源:https://stackoverflow.com/questions/15313579/why-are-the-transactions-rolled-back-even-when-propagation-propagation-requires

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!