处理事务中特定的 DataIntegrityViolationException

发布于 2025-01-19 05:47:37 字数 2705 浏览 0 评论 0原文

我有一个非常基本的创建用户控制器。

    @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<String> createUser(@RequestBody UserInput userInput) {
        userControllerService.createUser(userInput);
        return ResponseEntity.ok("success");
    }

UserControllerService#createUser 方法是一个包含多个 SimpleJpaRepository#save 调用的事务。例如,

    @Transactional
    @Override
    public void createUser(UserInput userInput) {
        userRepository.save(userInput);
        profileRepository.save(userInput.getProfile());
    }

我希望能够让数据库处理唯一约束违规,并能够通知客户端特定的违规。

例如,如果我想当且仅当我遇到特定的约束违规时才通知客户。

    @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<String> createUser(@RequestBody UserInput userInput) {
        try {
            userControllerService.createUser(userInput);
        } catch (DuplicateUserNameException e) {
            return new ResponseEntity<>("", HttpStatus.BAD_REQUEST);
        } catch (DuplicateEmailException e) {
            return new ResponseEntity<>("", HttpStatus.BAD_REQUEST);
        } catch (Exception e) {
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
        return ResponseEntity.ok("success");
    }

但是,任何约束违规都会在 UserControllerService#createUser 末尾引发 DataIntegrityViolationException。而且 DataIntegrityViolationException 太脆弱,无法依赖。它只有原因 SQLState: 23505(违反唯一约束)和非结构化消息,例如:

错误:重复的键值违反了唯一约束“unique_user” 详细信息:密钥(电子邮件)=([电子邮件受保护]) 已存在。

即使我添加自定义异常处理,它也永远不会运行,因为直到第一次实际调用数据库时,直到方法结束时才会遇到 DataIntegrityViolationException。例如,这没有效果。

    @Transactional
    @Override
    public void createUser(UserInput userInput) {
        try
          userRepository.save(userInput);
        } catch (Exception e) {
          throw new DuplicateUserNameException();
        }
        try {
          profileRepository.save(userInput.getProfile());
        } catch (Exception e) {
          throw new DuplicateEmailException();
        }
    }

我是否以错误的方式处理这个问题?看起来这是非常基本的功能,应该以某种方式实现。

我能想到的最好方法是添加一些代码来解析来自 DataIntegrityViolationException 的消息,但这有其局限性,例如,对于同一个表的两次插入对于应用程序具有不同的含义。第一个插入可能直接来自用户,第二个插入可能是应用程序生成的内容。仅通过解析详细消息可能无法从事务结束时区分两者。

我应该考虑其他实现吗?

I have a very basic create user controller.

    @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<String> createUser(@RequestBody UserInput userInput) {
        userControllerService.createUser(userInput);
        return ResponseEntity.ok("success");
    }

The UserControllerService#createUser method is a transaction containing multiple SimpleJpaRepository#save calls. E.g.

    @Transactional
    @Override
    public void createUser(UserInput userInput) {
        userRepository.save(userInput);
        profileRepository.save(userInput.getProfile());
    }

I would like to be able to have the db handle unique constraint violations and be able to inform the client about a specific violation.

For example if I want to inform the client if and only if I get specific constraint violations.

    @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<String> createUser(@RequestBody UserInput userInput) {
        try {
            userControllerService.createUser(userInput);
        } catch (DuplicateUserNameException e) {
            return new ResponseEntity<>("", HttpStatus.BAD_REQUEST);
        } catch (DuplicateEmailException e) {
            return new ResponseEntity<>("", HttpStatus.BAD_REQUEST);
        } catch (Exception e) {
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
        return ResponseEntity.ok("success");
    }

However any constraint violation throws a DataIntegrityViolationException at the end of UserControllerService#createUser. And DataIntegrityViolationException is too brittle to rely on. It only has the cause SQLState: 23505 (unique constraint violation) and an unstructured message, such as:

ERROR: duplicate key value violates unique constraint "unique_user"
Detail: Key (email)=([email protected]) already exists.

Even if I add custom exception handling, it will never be run since the DataIntegrityViolationException isn't encounter until the end of the method when the db is actually called for the first time. E.g. this has no effect.

    @Transactional
    @Override
    public void createUser(UserInput userInput) {
        try
          userRepository.save(userInput);
        } catch (Exception e) {
          throw new DuplicateUserNameException();
        }
        try {
          profileRepository.save(userInput.getProfile());
        } catch (Exception e) {
          throw new DuplicateEmailException();
        }
    }

Am I going about this the wrong way? It seems like this is very basic functionality that should be possible somehow.

The best way I can think of is adding some code to parse the message from DataIntegrityViolationException, but this has its limitations, for example, for two inserts into the same table have a different meaning for the application. One insert might be directly from the user and the second might be something the application generates. It may not be possible to distinguish the two from the end of the transaction by just parsing the detailed message.

Are there other implementations I should consider instead?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

成熟稳重的好男人 2025-01-26 05:47:37

如果我正确理解的话,听起来您想拥有一种可靠的方法来确定何时投掷dataintegrityViolationException是什么是导致它的确切原因,例如是由于重复的电子邮件还是重复的用户名。对于特定的用例或其他任何东西。

最简单的方法是不依靠抛出的例外来确定,而是在将数据保存到DB之前,积极发出某些SQL来对其进行验证,例如:

@Transactional
@Override
public void createUser(UserInput userInput) {

    if(userRepository.existUsername(userInput.getUsername()){
       throw new DuplicateUserNameException();
    }

    if(userRepository.existEmail(userInput.getEmail())){
      throw new DuplicateEmailException();
    }

    userRepository.save(userInput);
    profileRepository.save(userInput.getProfile());
}

If I understand correctly , it sounds like you want to have a reliable way to determine when DataIntegrityViolationException is thrown , what is the exact reason that causes it such as whether it is due to the duplicated email or duplicated username for a particular use case or anything else.

The simplest way is not to rely on the thrown exception to determine but actively issue some SQL to validate it before the data is saved to DB such as :

@Transactional
@Override
public void createUser(UserInput userInput) {

    if(userRepository.existUsername(userInput.getUsername()){
       throw new DuplicateUserNameException();
    }

    if(userRepository.existEmail(userInput.getEmail())){
      throw new DuplicateEmailException();
    }

    userRepository.save(userInput);
    profileRepository.save(userInput.getProfile());
}
却一份温柔 2025-01-26 05:47:37

你提到的问题

即使我添加自定义异常处理,它也永远不会运行,因为直到第一次实际调用数据库时,直到方法结束时才遇到 DataIntegrityViolationException。例如,这没有效果。

应该可以通过告诉 Hibernate 立即执行事务来解决,通过调用

userRepository.flush();

This 至少应该导致在该行上抛出异常。

The problem you mentioned

Even if I add custom exception handling, it will never be run since the DataIntegrityViolationException isn't encounter until the end of the method when the db is actually called for the first time. E.g. this has no effect.

should be solvable by telling Hibernate to execute the transaction right away, by calling

userRepository.flush();

This should cause the exception to be thrown on that line, at least.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文