问题
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.
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 !)
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 exampleUserService2
), and then inject the new bean intoUserService
and call that method using theuserService2.addUser2()
. - Inject
UserService
intoUserService
(I'm not sure if it can be done), and call theaddUser2()
by usinguserService.addUser2()
notthis.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