为什么即使在Spring服务类的第二种方法中传播= Propagation.REQUIRES_NEW时,事务也会回滚?
基本设置现在都很好,我开始尝试交易。 Struts + Spring + Hibernate注释事务管理器。 这是Action中的示例代码,将调用服务类:
userService.addUser();
这是服务类中的addUser()
方法:
@Transactional(value="deu" ) public void addUser() { userDao.addUser(); this.addUser2(); }
首先,我在userDao中调用了addUser,它将插入一个用户。 其次,我在此服务类的另一个方法中调用了addUser2
。
@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(); }
由于无效PK,这一次将失败。 我想第二次调用( addUser2
)会失败,但不会影响前一次调用。 但是,未插入用户。
如果我只打电话:
@Transactional(value="deu" ) public void addUser() { userDao.addUser(); //this.addUser2(); }
它正在工作,这意味着像数据库这样的基本设置没有错。
任何想法?
Spring的声明式事务处理使用AOP代理。 当你得到一个tranasactional bean时,你实际上得到一个包装你的bean实例的代理,拦截方法调用,必要时启动一个事务,然后调用实际bean的方法,然后在必要时提交或回滚事务。
但是你从同一个bean中的另一个方法调用bean的方法,因此代理被绕过,并且不能应用任何事务行为。
将该方法放在另一个bean中,或者使用AspectJ,它可以检测字节代码并拦截bean内方法调用。
有关更详细的说明,请参阅Spring文档 。
我做了一些测试并找到了结论。
-
如果第二个服务(内部)是必需的并抛出exception,即使是第一个事务捕获它,它们都会回滚(因为它们在同一条船上!)
-
如果第二个服务(内部)是REQUIRES_NEW并抛出exception,外部必须处理这个回滚(这不是我的回滚,但我必须做一些事情),如果没有捕获它,外部的这个exception将触发外部回滚(甚至这不是他的例外,但它是一个例外!)。 所以外部必须为这种情况做一些事情(设置回滚或捕获它)。
这样对吗 ?
这是因为Spring AOP架构。
Spring AOP使用代理,因此在代理上调用方法时只执行方面。
调用this.addUser2(...)
您调用自身对象上的方法,而不是代理。 因此,不执行任何方面,也不执行TX管理。
你可以做以下事情:
- 将
addUser2
方法移动到另一个bean(例如UserService2
),然后将新bean注入UserService
并使用userService2.addUser2()
调用该方法。 - 将
UserService
注入UserService
(我不确定是否可以完成),并使用userService.addUser2()
而不是this.addUser2()
调用addUser2()
this.addUser2()
。
就像Amir Pashazadeh说他不知道如何在同一个bean中使用事务上下文调用代理一样,这是一个例子:
@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); } }
请注意,如果您在addUser2中捕获了exception但是事务已标记为Rollback,则Spring似乎抛出UnexpectedRollbackException 。
- 每个“容器”类一个DAO或每个表一个DAO?
- Hibernate开启/关闭会话,DAO的正确方法
- 从另一个DAO调用一个DAO?
- Java EE DAO / DTO(数据传输对象)设计模式
- unit testing使用Spring JDBC的DAO类
- 使用Annotations使用Spring Hibernate编写更少的DAO
- hibernate删除错误:批量更新返回意外行计数
- 测试DAO时出错:sun.reflect.generics.reflectiveObjects.TypeVariableImpl无法强制转换为java.lang.Class
- Java从DAO中删除重复的try,catch,最后的样板