【问题标题】:Catch DataIntegrityViolationException in @Transactional service method在@Transactional 服务方法中捕获 DataIntegrityViolationException
【发布时间】:2016-07-29 15:24:36
【问题描述】:

我有一个带有 Hibernate 的 REST Spring Boot 应用程序。为简单起见,我们假设以下工作流程:

  1. 控制器处理传入请求,调用服务方法
  2. 服务方法为@Transactional,做一些业务逻辑,调用Persistence方法
  3. 持久性方法由 DAO 对象处理,将内容保存到数据库中。

数据库对用户的username 有一个unique 约束。我现在的工作方式是这样的:

  1. 客户端向控制器发送请求
  2. 控制器调用服务
  3. 服务尝试通过 DAO 保存对象。如果出现DataViolationException,Service 返回一个自定义的Exception
  4. 控制器捕获自定义异常并发送回适当的响应

伪代码是这样的:

public class UserController {
    @RequestMapping("/user")
    public User createUser(...){
        try{
            return userService.createUser(...);
        } catch (UserAlreadyExistsException e){
            // Do some processing and return error message to client
        }
    }
}

public class UserService {
    @Transactional
    public User createUser(...){
        (...)
        try{
            userDAO.save(newUserObject);
        } catch(DataIntegrityViolationException e){
            throw new UserAlreadyExistsException(username);
        }
    }
}

但是,这样在尝试创建重复用户时会出错。

javax.persistence.RollbackException: Transaction marked as rollbackOnly

解决此问题的一种方法似乎是让DataIntegrityViolationException 从事务中“冒泡”(而不是在服务中捕获它)。但这意味着 Controller 必须处理持久性异常,我不喜欢这样。

如果服务抛出“可理解的”异常供控制器处理,我更喜欢。该服务知道什么时候会出现持久性异常,并且能够将广泛的DataIntegrityViolationException“翻译”成有意义的。

有没有办法以这种方式处理异常?我并不特别喜欢使用“2 层服务层”来实现这一点的想法。


编辑:我想抛出自定义异常的另一个原因是编译器需要捕获它。我想强制控制器处理所有可能发生的异常。

【问题讨论】:

  • 然后告诉@Transactional 它应该回滚那些异常。默认情况下它只回滚RuntimeExceptions。
  • 您是否找到了一种方法来捕获 Service 而不是 Controller 中的异常?我正在考虑删除 @Transaction 并自己处理提交和回滚。
  • 请尝试:@Transactional(noRollbackFor = DataIntegrityViolationException.class)

标签: java spring hibernate transactions


【解决方案1】:

您的存储库需要扩展 JpaRepository,并且当您这样做时。您可以使用该存储库中的 saveAndFlush 方法。这意味着,您的代码将立即在数据库上执行,并且在完成事务之前将抛出异常,您将能够在 Catch 块中捕获它。我还添加了删除操作的示例。

存储库:

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserDAO extends JpaRepository<User, Long> {
}

服务:

 public class UserService {
        private UserDAO userDAO;

        (...)

        @Transactional
        public User createUser(...){
            (...)
            try{
                userDAO.saveAndFlush(newUserObject);
            } catch(DataIntegrityViolationException e){
                throw new UserAlreadyExistsException(username);
            }
        }

        @Transactional
        public void deleteUser(...){
            (...)
            try{
                userDAO.delete(deletingUserObject);
                userDAO.flush();
            } catch(DataIntegrityViolationException e){
                throw new UserException(username);
            }
        }
    }

【讨论】:

    【解决方案2】:

    注释你的服务方法
    @Transactional(rollbackFor = UserAlreadyExistsException.class)
    

    如果抛出异常,它会告诉 spring 不要提交事务,这样你就可以在你的控制器中捕获它

    【讨论】:

    • “理解”哪个约束被打破的最佳方式是什么,例如,如果一个实体上有多个约束?还是对不同实体的约束?
    • 没错,如果我有 2 个唯一约束如何区分它们?
    猜你喜欢
    • 1970-01-01
    • 2012-10-19
    • 1970-01-01
    • 1970-01-01
    • 2016-09-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-03
    相关资源
    最近更新 更多