为什么事务没有在 Spring JPA 中针对必需的传播级别进行回滚?

Why transaction not getting rollbacked in Spring JPA for REQUIRED propagation level?

我在 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: [ from spring Docs ]

PROPAGATION_REQUIRES_NEW, in contrast to PROPAGATION_REQUIRED, uses a completely independent transaction for each affected transaction scope. In that case, the underlying physical transactions are different and hence can commit or roll back independently, with an outer transaction not affected by an inner transaction’s rollback status.

代理

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

现在,这个“来自外界的任何呼叫者都不会直接与您交谈”非常重要。如果您进行内部调用,就像您在调用 persistLineManager 中的 persistEmployee 中所做的那样,则您不会通过代理。您直接调用您的方法,无需代理。因此,persistLineManager 方法顶部的注释未被读取。

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

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

@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_REQUIRES:

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

示例:

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

希望对您有所帮助