ConstraintViolationException e 无法执行解决方案

在事务中,发现无法捕获  ConstraintViolationException 异常,折腾了很久,最终解决了,怪自己当初异常那部分没好好看。

背景如下

我的需求是,当用户修改资料的时候,字段长度不合法的时候,会给以提示。前端是使用 bootstrapValidator 提示,当用户绕过 bootstrapValidator 限制,我们依然可以通过后端来限制。比如 Hibernate (或者 Spring Data JPA) 的验证注解 @NotEmpty,@Size 等来帮我们验证,如果验证不通过会抛一个 ConstraintViolationException 异常,我们可以格式化该异常的信息,然后返回给前台。

 

具体实例如下

User 实体

  1. @Entity
  2. @Data
  3. public class User implements UserDetails, Serializable {
  4.     private static final long serialVersionUID = 6147345506206285446L;
  5.     @Id // 主键
  6.     @GeneratedValue(strategy = GenerationType.IDENTITY) // 自增长策略
  7.     private Long id; // 用户的唯一标识
  8.     @NotEmpty(message = "昵称不能为空")
  9.     @Size(min = 2, max = 20, message = "昵称长度必须为2-20个字符")
  10.     @Column(nullable = false, length = 20)
  11.     private String nickname;
  12.     @NotEmpty(message = "邮箱不能为空")
  13.     @Size(max = 50, message = "邮箱长度最多50个字符")
  14.     @Email(message = "邮箱格式不对")
  15.     @Column(nullable = false, length = 50, unique = true)
  16.     private String email;
  17.     @NotEmpty(message = "用户名不能为空")
  18.     @Size(min = 4, max = 20, message = "用户名长度必须为4-20个字符")
  19.     @Column(nullable = false, length = 20, unique = true)
  20.     private String username; // 用户账号,用户登录时的唯一标识
  21.     @NotEmpty(message = "密码不能为空")
  22.     @Size(max = 100, message = "密码长度最多100个字符")
  23.     @Column(length = 100)
  24.     private String password; // 登录时密码
  25. }

 

Controller

  1. /**
  2.  * 保存基本资料
  3.  */
  4. @PostMapping("/profile")
  5. public ResponseEntity<Response> saveBasicProfile(User user, Principal principal,
  6.                                                  HttpSession session) {
  7.     User originalUser = userService.getUserByUsername(principal.getName());
  8.     try {
  9.         userService.saveUser(originalUser);
  10.     } catch (ConstraintViolationException e) {
  11.         return ResponseEntity.ok().body(new Response(false, ConstraintViolationExceptionHandler.getMessage(e)));
  12.     } catch (Exception e) {
  13.         return ResponseEntity.ok().body(new Response(false, e.getMessage()));
  14.     }
  15.     session.setAttribute("user", originalUser);
  16.     return ResponseEntity.ok().body(new Response(true"修改成功"));
  17. }

 

其中 ConstraintViolationExceptionHandler 类是自己写的,用来格式化异常信息

  1. public class ConstraintViolationExceptionHandler {
  2.     /**
  3.      * 获取批量异常信息
  4.      * @param e
  5.      * @return
  6.      */
  7.     public static String getMessage(ConstraintViolationException e) {
  8.         List<String> msgList = new ArrayList<>();
  9.         for (ConstraintViolation<?> constraintViolation : e.getConstraintViolations()) {
  10.             msgList.add(constraintViolation.getMessage());
  11.         }
  12.         String messages = StringUtils.join(msgList.toArray(), ";");
  13.         return messages;
  14.     }
  15. }

 

Service

  1. @Transactional
  2. @Override
  3. public void saveUser(User user) {
  4.     //添加用户
  5.     if (user.getId() == null) {
  6.         userRepository.save(user);
  7.     } else {
  8.         //更新用户
  9.         User originalUser = getUserById(user.getId());
  10.         BeanUtils.copyProperties(user, originalUser);
  11.         userRepository.save(originalUser);
  12.     }
  13. }

 

问题如下

当修改资料,将昵称改成一个字(上面 User 实体里有限制昵称至少2个字符)

ConstraintViolationException e 无法执行解决方案

然后发现,并没有捕获到 ConstraintViolationException 异常,而是捕获到了 TransactionSystemException 异常,异常信息如下

org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction

ConstraintViolationException e 无法执行解决方案

 

显然,这不是我们想要的

 

解决方案

原因:ConstraintViolationException 异常是包裹在 TransactionSystemException 里的,我们如果直接捕获它,可能永远都捕获不到。

所以我们应该先捕获 TransactionSystemException 而不是 ConstraintViolationException,然后去找它的 getCause(),一层一层地找,直到找到 ConstraintViolationException,我们就可以做事了,无论是中断还是返回。

通过 Debug 我们发现 ConstraintViolationException 被 RollbackException 包裹,RollbackException 又被 TransactionSystemException 包裹,我们只需要捕获 TransactionSystemException 异常,然后通过getCause() 方法来找我们需要的异常就行了。

所以应该调用直到遇到违反约束的 getCause()方法 Exception

最终的解决方案就是,改写 Controller 里的捕获异常的部分

  1. try {
  2.     userService.saveUser(originalUser);
  3. catch (TransactionSystemException e) {
  4.     Throwable t = e.getCause();
  5.     while ((t != null) && !(t instanceof ConstraintViolationException)) {
  6.         t = t.getCause();
  7.     }
  8.     if (t instanceof ConstraintViolationException) {
  9.         return ResponseEntity.ok().body(new Response(false, ConstraintViolationExceptionHandler.getMessage((ConstraintViolationException) t)));
  10.     }
  11. catch (Exception e) {
  12.     return ResponseEntity.ok().body(new Response(false, e.getMessage()));
  13. }

 

现在就能进入 t instanceof ConstraintViolationException 里了,前台效果图如下

ConstraintViolationException e 无法执行解决方案

 

参考地址:https://stackoverflow.com/questions/17384008/cant-catch-constraintviolationexception

本文地址:https://liuyanzhao.com/8000.html

  • 微信
  • 赶快加我聊天吧
  • weinxin
  • 博客/Java交流群
  • 资源分享,问题解决,技术交流。群号:590480292
  • weinxin
言曌

发表评论

:?::razz::sad::evil::!::smile::oops::grin::eek::shock::???::cool::lol::mad::twisted::roll::wink::idea::arrow::neutral::cry::mrgreen: