jpa 处理事务中的特定DataIntegrityViolationException

gab6jxml  于 2022-11-14  发布在  其他
关注(0)|答案(2)|浏览(132)

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

@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#保存调用的事务。

@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”详细信息:密钥(电子邮件)=(john@test.com)已存在。
即使我添加了自定义异常处理,它也永远不会运行,因为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的消息,但这有其局限性,例如,对同一个表的两次插入对应用程序有不同的意义。一次插入可能直接来自用户,第二次插入可能是应用程序生成的内容。仅通过解析详细消息可能无法在事务结束时区分这两者。
我是否应该考虑其他实现?

ubbxdtey

ubbxdtey1#

如果我没理解错的话,您似乎希望有一种可靠的方法来确定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());
}
j1dl9f46

j1dl9f462#

你提到的问题
即使我添加了自定义异常处理,它也永远不会运行,因为DataIntegrityViolationException直到第一次实际调用数据库的方法结束时才遇到。例如,这没有任何效果。
应该可以通过通知Hibernate立即执行事务来解决,方法是调用

userRepository.flush();

这至少会导致在该行上引发异常。

相关问题