为什么事务没有在 Spring JPA 中为 REQUIRED 传播级别回滚?

Gov*_*are 0 java spring jpa spring-transactions

我在 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!");
}
Run Code Online (Sandbox Code Playgroud)

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

PROPAGATION_REQUIRES_NEW:[来自 spring Docs]

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

RUA*_*ult 5

代理

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

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

因此,当persistLineManager抛出 a 时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!");
}
Run Code Online (Sandbox Code Playgroud)

默认情况下,@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;
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

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

传播

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

传播_必需: 传播_必需

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

传播_要求: PROPAGATION_REQUIRES_NEW

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

例子:

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

希望能帮助到你