为什么事务没有在 Spring JPA 中针对必需的传播级别进行回滚?
Why transaction not getting rollbacked in Spring JPA for REQUIRED propagation level?
我在 JPA 存储库中有两个方法。这两种方法的传播级别都是 REQUIRED
这些方法用于使用 Hibernate
到 Postgresql
来持久化实体对象
@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" 中。
希望对您有所帮助
我在 JPA 存储库中有两个方法。这两种方法的传播级别都是 REQUIRED
这些方法用于使用 Hibernate
到 Postgresql
@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" 中。
希望对您有所帮助