在Spring中的@Transaction方法中处理异常

43

我正在尝试弄清楚如何与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

不行...控制器没有处理来自数据库的异常的业务。

5. 不要捕获DataIntegrityViolationException

我看到过网上的几个资源,特别是与Hibernate结合使用时,建议捕获这个异常是错误的,并且人们应该在保存之前检查条件(即使用手动查询检查用户名是否存在)。在并发情况下,即使使用事务,这种方法也行不通。是的,您将获得事务的一致性,但仍然会在“其他人先到”的情况下收到DataIntegrityViolationException。因此,这不是一个可接受的解决方案。

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

使用Spring的TransactionTemplate而不是@Transactional。这是唯一比较令人满意的解决方案。但是,它比“只是在方法上添加@Transactional”要复杂得多,即使Spring文档似乎也倾向于使用@Transactional

我希望能得到有关如何最好处理这种情况的建议。还有更好的选择吗?


2
为什么不作为(3)的变体,在同一个服务中有两种方法?一种是@Transactional,另一种是捕获DataIntegrityViolationException - Markus Pscheidt
3
遇到相同的问题,我会使用这个变体的(3),听起来是最好的方法。别忘了把这第二种方法也公开,因为它应该能够被“切面化”(可能取决于你使用的aop框架)。 - Jens
嗨,你尝试过使用@ControlerAdvice吗?如果没有,可以在这里看一下(https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc)。 - alexandrum
基本上是第四点。根据MVC,控制器层不应处理来自数据库的异常。 - diesieben07
3个回答

3

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

  1. 自定义注解。
public @interface InterceptExceptions
{
}
  1. Spring容器中的Bean和Aspect。
<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(...)
  {
    // ...
  }
}

1

投票给(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;
    }
}

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接