ConstraintViolationException e 无法执行解决方案 获取批量异常信息格式化

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

 

Controller

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

 

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

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

 

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.  
  14. }

 

问题如下

当修改资料,将昵称改成一个字(上面 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 无法执行解决方案

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值