在 Spring 中的 @Transactional 方法期间处理异常

Posted

技术标签:

【中文标题】在 Spring 中的 @Transactional 方法期间处理异常【英文标题】:Handling exceptions during a @Transactional method in Spring 【发布时间】:2018-10-13 12:11:48 【问题描述】:

我试图找出如何结合 Spring 的@Transactional 最好地处理持久性(以及可能的其他)异常。 在这篇文章中,我将举一个简单的用户注册示例,由于用户名重复,可能会导致DataIntegrityViolationException

我尝试过以下几件事,但对我来说并不是很满意:

1。幼稚的方法:抓住异常

val entity = UserEntity(...)
try 
    repo.save(entity)
 catch (e: DataIntegrityViolationException) 
    // not included: some checks for which constraint failed
    throw DuplicateUsername(username) // to be handled by the controller

这在 @Transactional 方法中不起作用,因为在提交事务之前不会发生持久性异常,这发生在我在 spring 事务包装器中的服务方法之外。

2。退出前刷新EntityManager

在我的服务方法结束时在EntityManager 上显式调用flush。这将强制写入数据库并因此触发异常。但是它可能效率低下,因为我现在必须注意不要在请求期间无缘无故地刷新多次。我也最好永远不要忘记它,否则例外会消失得无影无踪。

3。创建两个服务类

@Transactional 方法放在一个单独的spring bean 中,并在主服务中围绕它们进行try-catch。这很奇怪,因为我必须注意将代码的一部分放在 A 位置,另一部分放在 B 位置。

4。在控制器中处理DataIntegrityViolationException

只是……不。控制器在处理来自数据库的异常时没有任何业务(hue hue hue)。

5。不要抓DataIntegrityViolationException

我在网上看到了一些资源,尤其是结合 Hibernate 的资源,这表明捕获此异常是错误的,应该在保存之前检查条件(即通过手动查询检查用户名是否存在)。这在并发场景中不起作用,即使使用事务也是如此。是的,您将获得与事务的一致性,但是当“其他人先来”时,您仍然会获得DataIntegrityViolationException。因此,这不是一个可接受的解决方案。

7。不要使用声明式事务管理

使用 Spring 的 TransactionTemplate 而不是 @Transactional。这是唯一有点令人满意的解决方案。然而,使用它比“只是在方法上抛出 @Transactional”要“笨拙”得多,甚至 Spring 文档似乎也在推动你使用 @Transactional

我想要一些关于如何最好地处理这种情况的建议。我上次提出的解决方案有更好的替代方案吗?

【问题讨论】:

为什么不作为 (3) 的变体,在同一个服务中有两种方法?一个是@Transactional,另一个是DataIntegrityViolationException 遇到同样的问题,我将使用 (3) 的这种变体,听起来是最好的方法。不要忘记将第二种方法也公开,因为它应该能够被“分割”(可能取决于您使用的 aop 框架)。 您好,您是否尝试过使用@ControlerAdvice?如果没有,也许看看here。 基本上就是第 4 点。根据 MVC,控制器层不应该处理来自数据库的异常。 【参考方案1】:

我在我的项目中使用以下方法。

    自定义注释。
public @interface InterceptExceptions


    Spring 上下文中的 Bean 和方面。
<beans ...>
  <bean id="exceptionInterceptor" class="com.example.ExceptionInterceptor"/>

  <aop:config>
    <aop:aspect ref="exceptionInterceptor">
      <aop:pointcut id="exception" expression="@annotation(com.example.InterceptExceptions)"/>
      <aop:around pointcut-ref="exception" method="catchExceptions"/>
    </aop:aspect>
  </aop:config>
</beans>
import org.aspectj.lang.ProceedingJoinPoint;

public class ExceptionInterceptor


  public Object catchExceptions(ProceedingJoinPoint joinPoint)
  
    try
    
      return joinPoint.proceed();
    
    catch (MyException e)
    
      // ...
    
    

    最后是用法。
@Service
@Transactional
public class SomeService

  // ...

  @InterceptExceptions
  public SomeResponse doSomething(...)
  
    // ...
  

【讨论】:

【参考方案2】:

您可以使用带有@ControllerAdvice 或@RestControllerAdvice 注解的类来处理异常

当控制器抛出异常时,您可以在此类中捕获它并将响应状态更改为合适的状态或添加异常的额外信息

此方法可帮助您保持干净的代码

你有很多例子:

https://www.javainuse.com/spring/boot-exception-handling

https://dzone.com/articles/best-practice-for-exception-handling-in-spring-boo

【讨论】:

【参考方案3】:

投票给(3),喜欢:

@Service
public class UserExtService extend UserService


@Service
public class UserService 
    @Autowired
    UserExtService userExtService;

    public int saveUser(User user) 
        try 
            return userExtService.save(user);
         catch (DataIntegrityViolationException e) 
          throw DuplicateUsername(username);// GlobalExceptionHandler to response
        
        return 0;
    

    @Transactional(rollbackFor = Exception.class)
    public int save(User user) 
        //...
        return 0;
    

【讨论】:

以上是关于在 Spring 中的 @Transactional 方法期间处理异常的主要内容,如果未能解决你的问题,请参考以下文章

注意Spring事务这一点,避免出现大事务

注意Spring事务这一点,避免出现大事务

MongoDB4.2分布式事务

oracle rollback 观察时间

重学SpringBoot系列之Mockito测试

区块链之智能合约 solidity踩坑 --上篇