为啥即使在 Spring 服务类的第二个方法中传播 = Propagation.REQUIRES_NEW 时事务也会回滚?
Posted
技术标签:
【中文标题】为啥即使在 Spring 服务类的第二个方法中传播 = Propagation.REQUIRES_NEW 时事务也会回滚?【英文标题】:Why are the transactions rolled back even when propagation=Propagation.REQUIRES_NEW in second method in Spring service class?为什么即使在 Spring 服务类的第二个方法中传播 = Propagation.REQUIRES_NEW 时事务也会回滚? 【发布时间】:2013-02-25 03:41:01 【问题描述】:现在基本设置都很好,我开始尝试交易。 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();
它正在工作,这意味着像数据库这样的基本设置没有错。
有什么想法吗?
【问题讨论】:
【参考方案1】:Spring 的声明式事务处理使用 AOP 代理工作。当你得到一个事务性 bean 时,实际上你得到了一个代理,它包装你的 bean 实例,拦截方法调用,必要时启动事务,然后调用实际 bean 的方法,然后在必要时提交或回滚事务。
但是你从同一个 bean 中的另一个方法调用你的 bean 的一个方法,所以代理被绕过了,并且不能应用任何事务行为。
将方法放在另一个 bean 中,或者使用 AspectJ,它检测字节码并可以拦截 bean 内的方法调用。
更详细的解释见Spring documentation。
【讨论】:
嗨,JB:它仍然无法正常工作。我在 userService 中注入了另一个服务(countryService),在 addUser 方法中,我调用了 dao.addUser(这个将插入一个用户),然后是 countryService.addCountry(这个是propagation=Propagation.REQUIRES_NEW,并且由于空PK而失败)。但是,第一个添加的用户也会被回滚……我现在不知道为什么了~ 嗨,JB Nizet:我犯了你写的关于 requires_new 的另一篇文章,我想我误解了 Spring 中的某些内容。我可以再问一下,如果内部事务回滚并将其异常抛出给外部事务,外部事务是否也回滚?(如果外部不做任何事情)如果是这样,如果内部被更改,则需要并且这次再次抛出异常和外部抓住它不做任何事情,这两种情况是否意味着同一件事?如果这是真的,那么我声明的没有用......它只取决于我的程序逻辑! :( 我在答案中写下了我的发现,希望是对的,请再看一遍,谢谢! 你说得对。如果第二个服务使用 REQUIRED,则两个服务都在同一个事务中,如果第二个(或第一个)服务抛出异常,该事务将被标记为回滚。在第一个服务中捕获异常不会改变任何事情:事务已被标记为回滚。如果第二个服务使用 REQUIRES_NEW 并抛出异常,则它有自己的事务,该事务将被回滚。但是,与任何其他异常一样,该异常将传播到调用者(第一个服务)。所以如果它没有捕捉到它,它的事务也会被回滚。【参考方案2】:我做了一些测试,找到了结论。
如果需要第二个服务(内部)并抛出异常,即使第一个事务捕获它,它们都会回滚(因为它们在同一条船上!)
1234563触发外部回滚(即使它不是他的异常,但它是一个异常!)。所以外部必须为这种情况做点什么(设置回滚或捕获它)。对吗?
【讨论】:
【参考方案3】:这是因为 Spring AOP 架构。
Spring AOP 使用代理,所以方面只是在代理上调用方法时执行。
调用this.addUser2(...)
时,您调用的是self 对象上的方法,而不是代理。所以没有执行任何方面,也没有进行 TX 管理。
你可以做以下事情:
将addUser2
方法移动到另一个bean(例如UserService2
),然后将新bean 注入UserService
并使用userService2.addUser2()
调用该方法。
将UserService
注入UserService
(我不确定是否可以这样做),并使用userService.addUser2()
而不是this.addUser2()
调用addUser2()
。
【讨论】:
您的解决方案应该可以工作,但是,另一个被声明为 Propagation.REQUIRES_NEW 的服务(现在已经注入)在第一次调用时回滚我插入的用户。【参考方案4】:正如 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 中捕获异常但事务已标记为回滚,则 Spring 似乎会抛出 UnexpectedRollbackException。
【讨论】:
以上是关于为啥即使在 Spring 服务类的第二个方法中传播 = Propagation.REQUIRES_NEW 时事务也会回滚?的主要内容,如果未能解决你的问题,请参考以下文章
LayoutInflater类的inflate方法中的第二个参数怎么用,Android
为啥对方法的第二个参数使用 await 运算符会影响第一个参数的值?