发生异常后,如何使用 PostgreSQL 在 Spring Boot 中继续事务?

Posted

技术标签:

【中文标题】发生异常后,如何使用 PostgreSQL 在 Spring Boot 中继续事务?【英文标题】:How can I continue a transaction in Spring Boot with PostgreSQL after an Exception occured? 【发布时间】:2017-10-07 10:42:45 【问题描述】:

我创建了一个创建用户帐户的服务方法。如果由于给定的电子邮件地址已经在我们的数据库中而导致创建失败,我想向用户发送一封电子邮件,说明他们已经注册:

@Transactional(noRollbackFor=DuplicateEmailException.class)
void registerUser(User user) 
   try 
      userRepository.create(user);
   catch(DuplicateEmailException e) 
      User registeredUser = userRepository.findByEmail(user.getEmail());
      mailService.sendAlreadyRegisteredEmail(registeredUser);
   

这不起作用。虽然我将DuplicateEmailExcepetion 标记为“无回滚”,但第二个 SQL 查询 (findByEmail) 仍然失败,因为事务已中止。

我做错了什么?

仓库中没有@Transactional注解。

【问题讨论】:

如果您在 try 本身中尝试 catch 块中的 sql 部分,例如在创建之前检查用户是否存在,这可能会有所帮助! 当发生运行时异常(这就是违反数据完整性的原因)时,事务将被回滚(实际上它会被标记为回滚)。我怀疑您的存储库UserRepository 以及您的服务都标有@Transactional(由于调用链中存在另一个事务拦截器,这将导致这种行为)。 不,存储库中没有 @Transactional 注释。我试过了,但它并没有改变行为 我刚刚检查了堆栈跟踪。服务和存储库之间没有TransactionInterceptor。只有在控制器和服务之间有一个 你能详细介绍一下userRepository.create(user);方法吗?您是否检查过DuplicateEmailException 是由userRepository.create(user) 方法抛出的? 【参考方案1】:

这不是 Spring / JDBC 或您的代码的问题,问题在于底层数据库。例如,当您使用 Postgres 时,如果事务中的任何语句失败,则所有后续语句都将失败并显示 current transaction is aborted

例如在 Postgres 上执行以下语句:

> start a transaction
> DROP SEQUENCE BLA_BLA_BLA;
> Error while executing the query; ERROR: sequence "BLA_BLA_BLA" does not exist"
> SELECT * FROM USERS;
> ERROR: current transaction is aborted, commands ignored until end of transaction block

SELECT 和后续语句仍有望成功针对 mysql、Oracle 和 SQL Server

【讨论】:

谢谢,你让我开心。 +100 所以要在 postgres 中解决这个问题,只需执行回滚命令。欲了解更多详情,请访问laurenthinoul.com/… OP 想知道如何解决这个问题,但你告诉他为什么会发生这个错误,这不是很有帮助。 @raven 当您知道问题出在哪里时,这只是实施问题,有 100 种方法可以解决 OP 遇到的问题,他得到了答案,为什么那么如何解决它只是实施细节我不打算分享。【参考方案2】:

你为什么不改变逻辑如下:

void registerUser(User user) 
   User existingUser = userRepository.findByEmail(user.getEmail())
   if(existingUser == null)
         userRepository.create(user);
   else
       mailService.sendAlreadyRegisteredEmail(existingUser)
   

这将确保只有不存在的用户被插入到数据库中。

【讨论】:

是的,当然可以。我只是想知道为什么 spring-boot 会取消交易,尽管我告诉它不要这样做 定义的 NnoRollbackFor (docs.spring.io/spring/docs/current/javadoc-api/org/…) 定义零 (0) 个或多个异常类,它们必须是 Throwable 的子类,指示哪些异常类型不能导致事务回滚.. 你的 DuplicateEmailException 类是否遵循这些规则? DuplicateEmailException 扩展了 RuntimeException,它是 Exception 的子类,它是 Throwable 的子类。所以是的,我认为我遵循这些规则 我认为下面的链接解释得很好:***.com/questions/27849968/… 嗯,它很相似,但并不完全相同。他有两个嵌套的@Transactional 注释,而我只有一个。另外我没有使用 Hibernate,而是使用 JdbcTemplate【参考方案3】:

@Transactional 注释放置不正确。 Spring 围绕定义 @Transactional 注释的方法创建 AOP 顾问。因此,在这种情况下,将围绕registedUser 方法创建切入点。但是,registerUser 方法不会抛出 DuplicateEmailException。因此,不会评估回滚规则。

您需要围绕UserRepository.createUser 方法定义@Transactional 规则。这将确保spring创建的事务切入点不会因为DuplicateEmailException而回滚。

public class UserRepository 

    @Transactional(noRollbackFor=DuplicateEmailException.class)
    public User createUser()
        //if user exist, throw DuplicateEmailException
    


void registerUser(User user) 
    try 
       userRepository.create(user);
    catch(DuplicateEmailException e) 
       User registeredUser = userRepository.findByEmail(user.getEmail());
       mailService.sendAlreadyRegisteredEmail(registeredUser);
    

【讨论】:

不,它放置得很完美。我希望新用户的注册作为一次交易发生。否则,当代码变得更复杂时,我可能会导致部分注册过程被持久化而其他部分则不会。 Spring 事务确保如果事务已经到位(需要传播),请使用现有的事务。这允许开发人员将@Transactional 注解放在同一个执行堆栈中的多个方法上,而不会产生任何副作用。 是的,但是当事务被标记为回滚时,这适用于“内部”和“外部”事务,所以这无济于事 如果您在内部事务上设置回滚规则,那么它将正常工作,因为 spring 不会为 userRepository.create(user) 回滚您的事务。交易不会被标记为回滚。因此,您应该将所有内容视为一次提交。【参考方案4】:

您可以将对 userRepository 的调用包装在 try catch 块中。或者,如果用户存在,您可以先查看,然后中止创建新用户。

【讨论】:

已经有一个try-catch-block。请看我的示例代码 但不正确。看看蒂娜的回答。我们的答案是相同的,以防您不认识。

以上是关于发生异常后,如何使用 PostgreSQL 在 Spring Boot 中继续事务?的主要内容,如果未能解决你的问题,请参考以下文章

org.hibernate.AssertionFailure: [...] 条目中的空 id(发生异常后不要刷新会话)

PostgreSQL存储过程-异常错误处理

处理 PostgreSQL 异常的优雅方式?

python如何监控PostgreSQL代码运行

postgresql: prepare transaction

如何在异常发生后立即停止执行函数? [Python]