为啥即使在 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

为啥这个的第二个版本在指数时间内运行?

为啥说重定向的第二个请求方法一定是get方法

为啥对方法的第二个参数使用 await 运算符会影响第一个参数的值?

为啥 ios 中的通知仅显示附件数组中的第一个附件,即使还有一个附件。

即使在 C 中使用“\n%.2f”后,也无法将浮点值舍入到最接近的第二个小数 [关闭]