在事务中,发现无法捕获 ConstraintViolationException 异常,折腾了很久,最终解决了,怪自己当初异常那部分没好好看。
背景如下 我的需求是,当用户修改资料的时候,字段长度不合法的时候,会给以提示。
前端是使用 bootstrapValidator 提示,当用户绕过 bootstrapValidator 限制,我们依然可以通过后端来限制。
比如 Hibernate (或者 Spring Data JPA) 的验证注解 @NotEmpty,@Size 等来帮我们验证,如果验证不通过会抛一个 ConstraintViolationException 异常,我们可以格式化该异常的信息,然后返回给前台。
具体实例如下
User 实体
- @Entity
- @Data
- public class User implements UserDetails, Serializable {
- private static final long serialVersionUID = 6147345506206285446L;
- @Id // 主键
- @GeneratedValue(strategy = GenerationType.IDENTITY) // 自增长策略
- private Long id; // 用户的唯一标识
- @NotEmpty(message = "昵称不能为空")
- @Size(min = 2, max = 20, message = "昵称长度必须为2-20个字符")
- @Column(nullable = false, length = 20)
- private String nickname;
- @NotEmpty(message = "邮箱不能为空")
- @Size(max = 50, message = "邮箱长度最多50个字符")
- @Email(message = "邮箱格式不对")
- @Column(nullable = false, length = 50, unique = true)
- private String email;
- @NotEmpty(message = "用户名不能为空")
- @Size(min = 4, max = 20, message = "用户名长度必须为4-20个字符")
- @Column(nullable = false, length = 20, unique = true)
- private String username; // 用户账号,用户登录时的唯一标识
- @NotEmpty(message = "密码不能为空")
- @Size(max = 100, message = "密码长度最多100个字符")
- @Column(length = 100)
- private String password; // 登录时密码
- }
Controller
- /**
- * 保存基本资料
- */
- @PostMapping("/profile")
- public ResponseEntity<Response> saveBasicProfile(User user, Principal principal,
- HttpSession session) {
- User originalUser = userService.getUserByUsername(principal.getName());
- try {
- userService.saveUser(originalUser);
- } catch (ConstraintViolationException e) {
- return ResponseEntity.ok().body(new Response(false, ConstraintViolationExceptionHandler.getMessage(e)));
- } catch (Exception e) {
- return ResponseEntity.ok().body(new Response(false, e.getMessage()));
- }
- session.setAttribute("user", originalUser);
- return ResponseEntity.ok().body(new Response(true, "修改成功"));
- }
其中 ConstraintViolationExceptionHandler 类是自己写的,用来格式化异常信息
- public class ConstraintViolationExceptionHandler {
- /**
- * 获取批量异常信息
- * @param e
- * @return
- */
- public static String getMessage(ConstraintViolationException e) {
- List<String> msgList = new ArrayList<>();
- for (ConstraintViolation<?> constraintViolation : e.getConstraintViolations()) {
- msgList.add(constraintViolation.getMessage());
- }
- String messages = StringUtils.join(msgList.toArray(), ";");
- return messages;
- }
- }
Service
- @Transactional
- @Override
- public void saveUser(User user) {
- //添加用户
- if (user.getId() == null) {
- userRepository.save(user);
- } else {
- //更新用户
- User originalUser = getUserById(user.getId());
- BeanUtils.copyProperties(user, originalUser);
- userRepository.save(originalUser);
- }
- }
问题如下
当修改资料,将昵称改成一个字(上面 User 实体里有限制昵称至少2个字符)
然后发现,并没有捕获到 ConstraintViolationException 异常,而是捕获到了 TransactionSystemException 异常,异常信息如下 org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction
显然,这不是我们想要的
解决方案
原因:ConstraintViolationException 异常是包裹在 TransactionSystemException 里的,我们如果直接捕获它,可能永远都捕获不到。 所以我们应该先捕获 TransactionSystemException 而不是 ConstraintViolationException,然后去找它的 getCause(),一层一层地找,直到找到 ConstraintViolationException,我们就可以做事了,无论是中断还是返回。
通过 Debug 我们发现 ConstraintViolationException 被 RollbackException 包裹,RollbackException 又被 TransactionSystemException 包裹,我们只需要捕获 TransactionSystemException 异常,然后通过getCause() 方法来找我们需要的异常就行了。
所以应该调用直到遇到违反约束的 getCause()
方法 Exception。
最终的解决方案就是,改写 Controller 里的捕获异常的部分
- try {
- userService.saveUser(originalUser);
- } catch (TransactionSystemException e) {
- Throwable t = e.getCause();
- while ((t != null) && !(t instanceof ConstraintViolationException)) {
- t = t.getCause();
- }
- if (t instanceof ConstraintViolationException) {
- return ResponseEntity.ok().body(new Response(false, ConstraintViolationExceptionHandler.getMessage((ConstraintViolationException) t)));
- }
- } catch (Exception e) {
- return ResponseEntity.ok().body(new Response(false, e.getMessage()));
- }
现在就能进入 t instanceof ConstraintViolationException 里了,前台效果图如下
参考地址:https://stackoverflow.com/questions/17384008/cant-catch-constraintviolationexception
您可以选择一种方式赞助本站
支付宝扫一扫赞助
微信钱包扫描赞助
赏