【问题标题】:Why transaction not getting rollbacked in Spring JPA for REQUIRED propagation level?为什么事务没有在 Spring JPA 中为 REQUIRED 传播级别回滚?
【发布时间】:2020-05-01 13:02:43
【问题描述】:

我在 JPA 存储库中有两种方法。这两种方法的传播级别都为REQUIRED 这些方法用于使用HibernatePostgresql 持久化实体对象

@Transactional(propagation = Propagation.REQUIRED)
public void persistEmployee() {
    Employee employee = new Employee("Peter", "Washington DC");
    entityManager.persist(employee);
    try {
        persistLineManager();
    }
    catch( Exception e ) {
         // some task
    }
}

@Transactional(propagation = Propagation.REQUIRED, rollbackFor = RuntimeException.class)
public void persistLineManager() {
    Employee lineManager = new Employee("John", "NYC");
    entityManager.persist(lineManager);
    if(lineManager != null) // intentionally! To trigger rollback
        throw new RuntimeException("Rollback!");
}

根据 Spring 文档,当传播级别为 REQUIRED 时,两种方法都将在同一个事务中运行。在我的代码中,我故意抛出异常来触发回滚,但两个实体仍然被持久化。但我相信这两个操作都应该回滚。如果我的理解不正确,请更正,并让我知道正确的方法 回滚这两个操作。

PROPAGATION_REQUIRES_NEW:[来自 spring 文档]

PROPAGATION_REQUIRES_NEW 与 PROPAGATION_REQUIRED 相比,对每个受影响的事务范围使用完全独立的事务。在这种情况下,底层物理事务是不同的,因此可以独立提交或回滚,外部事务不受内部事务回滚状态的影响。

【问题讨论】:

    标签: java spring jpa spring-transactions


    【解决方案1】:

    代理

    在您的服务中,您创建了 2 个方法,均为 @Transactional。当您创建 bean 时,spring 将创建一个代理,以便在运行时为您添加事务方法的行为。让我们更深入地了解: 该代理由图像说明。 任何来自外部世界的呼叫者都不会直接与您交谈,而是与您的代理交谈。然后,代理会调用你来执行你的服务代码。

    现在,“任何来自外界的呼叫者都不会直接与您通话”非常重要。如果您进行内部调用,就像您在调用persistLineManagerpersistEmployee 中所做的那样,那么您不会通过代理。你直接调用你的方法,NO PROXY。因此,persistLineManager 方法顶部的注释不会被读取。

    所以,当persistLineManager 抛出RuntimeException 时,异常被你的调用者persistEmployee 直接捕获,你直接进入你的catch。由于没有代理,所以没有回滚,因为事务代理没有捕获异常。

    如果你只这样做,你会发生回滚:

    @Transactional(propagation = Propagation.REQUIRED)
    public void persistEmployee() {
        Employee employee = new Employee("Peter", "Washington DC");
        entityManager.persist(employee);
        persistLineManager();
        // Don't catch and the exception will be catched by the transaction proxy, which will rollback
    }
    
    public void persistLineManager() {
        Employee lineManager = new Employee("John", "NYC");
        entityManager.persist(lineManager);
        if(lineManager != null) // intentionally! To trigger rollback
            throw new RuntimeException("Rollback!");
    }
    

    默认情况下,@Transactional 回滚 RuntimeException

    交易模板

    假设您仍然希望这两种方法都是独立的事务性方法,您可以使用TransactionTemplate。这是一个例子:

    class MyService {
        // Have a field of type TransactionTemplate
        private TransactionTemplate template;
    
        // In the constructor, Spring will inject the correct bean
        public MyService(PlatformTransactionManager transactionManager) {
            template = new TransactionTemplate(transactionManager);
            // Set this here if you always want this behaviour for your programmatic transaction
            template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        }
    
        // Here you start your first transaction when arriving from the outside
        @Transactional(propagation = Propagation.REQUIRED)
        public void persistEmployee() {
            Employee employee = new Employee("Peter", "Washington DC");
            entityManager.persist(employee);
            // Inner call
            try {
                persistLineManager();
            } catch (RuntimeException e) {
                // Do what you want
            }
        }
    
        public void persistLineManager() {
            // Here, ask to your transactionTemplate to execute your code.
            template.execute(status -> {
                Employee lineManager = new Employee("John", "NYC");
                entityManager.persist(lineManager);
                if(lineManager != null) // intentionally! To trigger rollback
                    throw new RuntimeException("Rollback!");
                return null;
            });
        }
    }
    

    我还没有测试所有东西,你可能会遇到一些错误,但我希望你能明白。

    传播

    让我添加最后一部分关于 PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 之间的区别:

    PROPAGATION_REQUIRED:

    • 要么我没有交易,然后我创建一个
    • 或者有一个正在运行的事务,我加入它。

    传播要求:

    • 在任何情况下,无论事务是否正在运行,我都会创建一个新事务。

    例子:

    • 客户正在使用 PROPAGATION_REQUIRED 进入我的事务方法。它创建一个事务名称“TA”。
    • 此事务性方法调用一个方法,该方法也是事务性的,但 PROPAGATION_REQUIRES_NEW。它创建名为“TB”的第二个事务
    • 现在有了:“client”->“TA”->“TB”
    • 但是第二种方法会触发回滚。在这种情况下,只有“TB”会被回滚,因为“TA”和“TB”是两个不同的事务。
    • 因此,在 DB 中,我将保留在“TA”中进行的所有操作,而不是在“TB”中。

    希望对你有帮助

    【讨论】:

    • 如果我想在单独的事务中运行persistLineManager,即使用PROPAGATION_REQUIRES_NEW 在上述情况下如何实现?知道这是一个内部调用,如果我无法将该方法移动到不同的存储库,
    猜你喜欢
    • 1970-01-01
    • 2012-10-14
    • 1970-01-01
    • 1970-01-01
    • 2012-09-25
    • 1970-01-01
    • 2022-08-18
    • 1970-01-01
    • 2020-04-28
    相关资源
    最近更新 更多