【问题标题】:SpringBoot doesn't handle org.hibernate.exception.ConstraintViolationExceptionSpringBoot 不处理 org.hibernate.exception.ConstraintViolationException
【发布时间】:2017-12-17 15:31:22
【问题描述】:

我在我的实体类中定义了一个验证电子邮件的模式。在我的验证异常处理程序类中,我为 ConstraintViolationException 添加了处理程序。我的应用程序使用 SpringBoot 1.4.5。

Profile.java

@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "profile")
public class Profile extends AuditableEntity {

  private static final long serialVersionUID = 8744243251433626827L;

  @Column(name = "email", nullable = true, length = 250)
  @NotNull
  @Pattern(regexp = "^([^ @])+@([^ \\.@]+\\.)+([^ \\.@])+$")
  @Size(max = 250)
  private String email;
....
}

ValidationExceptionHandler.java

@ControllerAdvice
public class ValidationExceptionHandler extends ResponseEntityExceptionHandler {

  private MessageSource messageSource;

  @Autowired
  public ValidationExceptionHandler(MessageSource messageSource) {
    this.messageSource = messageSource;
  }

  @ExceptionHandler(ConstraintViolationException.class)
  public ResponseEntity<Object> handleConstraintViolation(ConstraintViolationException ex,
  WebRequest request) {
    List<String> errors = new ArrayList<String>();
    ....
    }
} 

当我运行我的代码并传递无效的电子邮件地址时,我得到以下异常。 handleConstraintViolation 中的代码永远不会执行。异常中返回的 http 状态是 500,但我想返回 400。知道如何实现吗?

2017-07-12 22:15:07.078 ERROR 55627 --- [nio-9000-exec-2] o.h.c.s.u.c.UserProfileController        : Validation failed for classes [org.xxxx.common.service.user.domain.Profile] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='must match "^([^ @])+@([^ \.@]+\.)+([^ \.@])+$"', propertyPath=email, rootBeanClass=class org.xxxx.common.service.user.domain.Profile, messageTemplate='{javax.validation.constraints.Pattern.message}'}]

javax.validation.ConstraintViolationException: Validation failed for classes [org.xxxx.common.service.user.domain.Profile] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='must match "^([^ @])+@([^ \.@]+\.)+([^ \.@])+$"', propertyPath=email, rootBeanClass=class org.xxxx.common.service.user.domain.Profile, messageTemplate='{javax.validation.constraints.Pattern.message}'}]

at  org.hibernate.cfg.beanvalidation.BeanValidationEventListener.validate(BeanValidationEventListener.java:138)

at org.hibernate.cfg.beanvalidation.BeanValidationEventListener.onPreInsert(BeanValidationEventListener.java:78)    

【问题讨论】:

    标签: java spring-boot jpa-2.0


    【解决方案1】:

    您还可以捕获 ConstraintViolationException 并使用 @ResponseStatus 代码或其他代码抛出自己的异常,然后在 @ExceptionHandler(YourCustomException.class) 中捕获它。如果你想这样做,你需要实现 JpaRepository。在保存期间,您应该调用saveAndFlush 方法,这意味着您的代码将立即在数据库中执行,您将能够捕获异常我尝试捕获块。如果你愿意,你可以像这样通用:

    imports
    ...
    public class ErrorHandler {
    
        public static <T> T execute(Supplier<T> repositorySaveFunction) {
            try {
                return repositorySaveFunction.get();
            } catch (DataIntegrityViolationException e) {
                if (e.getCause() instanceof org.hibernate.exception.ConstraintViolationException) {
                    throw new CustomObjectAlreadyExistException();
                }
                if (e.getCause() instanceof PropertyValueException) {
                    var fieldName = ((PropertyValueException) e.getCause()).getPropertyName();
                    throw new CustomNotNullException(fieldName);
                }
                throw e;
            } catch (javax.validation.ConstraintViolationException e) {
                e.getConstraintViolations().forEach(constraintViolation -> {
                    throw new CustomNotNullException(constraintViolation.getPropertyPath());
                });
                throw e;
            }
        }
    }
    

    服务:

            imports
        ...
            @Service
            @Transactional
            public class Service {
            
                private final YourRepository yourRepository;
                ... constructor
            
                public ObjectToSave save(ObjectToSave objectToSave) {
                    return execute(() -> yourRepository.saveAndFlush(objectToSave));
                }
    
            }
    

    【讨论】:

      【解决方案2】:

      试试这个方法..

      @ControllerAdvice
      public class ControllerAdvisor extends ResponseEntityExceptionHandler {
      
          @Autowired
          BaseResponse baseResponse;
      
          @ExceptionHandler(javax.validation.ConstraintViolationException.class)
          public ResponseEntity<BaseResponse> inputValidationException(Exception e) {
      
              baseResponse.setMessage("Invalid Input : " + e.getMessage());
              return new ResponseEntity<BaseResponse>(baseResponse, HttpStatus.BAD_REQUEST);
      
          }
      }
      
      

      【讨论】:

        【解决方案3】:

        这是我的解决方案...

        @ExceptionHandler({DataIntegrityViolationException.class})
            protected ResponseEntity<Object> handlePersistenceException(final DataIntegrityViolationException ex) {
        
                Throwable cause = ex.getRootCause();
        
                if (cause instanceof SQLIntegrityConstraintViolationException) {
        
                    SQLIntegrityConstraintViolationException consEx = (SQLIntegrityConstraintViolationException) cause;
        
                    final ApiErrorResponse apiError =  ApiErrorResponse.newBuilder()
                            .message(consEx.getLocalizedMessage())
                            .status(HttpStatus.BAD_REQUEST)
                            .build();
        
                    return new ResponseEntity<>(apiError, new HttpHeaders(), apiError.getStatus());
                }
        
                final ApiErrorResponse apiError =  ApiErrorResponse.newBuilder()
                        .message(ex.getLocalizedMessage())
                        .status(HttpStatus.NOT_ACCEPTABLE)
                        .build();
        
                return new ResponseEntity<>(apiError, new HttpHeaders(), apiError.getStatus());
            }
        
        @ExceptionHandler(RollbackException.class)
           public ResponseEntity<ApiErrorsListResponse> handleNotValidException(RollbackException ex){
        
               String errMessage = ex.getCause().getMessage();
        
               List<String> listErrMessage = getListErrMessage(errMessage);
               ApiErrorsListResponse response = ApiErrorsListResponse.newBuilder()
                       .status(HttpStatus.NOT_ACCEPTABLE)
                       .errorMessage(listErrMessage)
                       .build();
        
               return new ResponseEntity<>(response, HttpStatus.NOT_ACCEPTABLE);
        
           }
        
            public static List<String> getListErrMessage(String msg){
        
                Stream<String> stream = Arrays.stream(msg.split("\n"))
                        .filter(s -> s.contains("\t"))
                        .map(s -> s.replaceAll("^([^\\{]+)\\{", ""))
                        .map(s -> s.replaceAll("[\"]", ""))
                        .map(s -> s.replaceAll("=", ":"))
                        .map(s -> s.replaceAll("interpolatedMessage", "message"))
                        .map(s -> s.replaceAll("\\{|\\}(, *)?", ""));
        
                return stream.collect(Collectors.toList());
            }
        
        • 豆子
        
        public class ApiErrorsListResponse {
        
            private HttpStatus status;
        
         private List<String> errorMessage;
        
            public ApiErrorsListResponse() {
            }
        ...
        }
        
        

        【讨论】:

          【解决方案4】:

          以下解决方案基于 Spring Boot 2.1.2。

          澄清事情......正如nimai已经正确提到的那样:

          您无法捕获 ConstraintViolationException.class,因为它不会传播到代码的该层,而是被较低层捕获、包装并在另一种类型下重新抛出。因此,影响您的 Web 层的异常不是 ConstraintViolationException

          在您的情况下,它可能是DataIntegrityViolationException,它指出了持久层中的一个问题。但是你不想让它走那么远。


          解决方案

          对作为方法参数给出的实体使用@Valid 注释,如Ena 提到的。在我的版本中,它缺少org.springframework.web.bind.annotation.RequestBody 注释(没有@RequestBody 注释,ProfileDto 无法正确解析为您的ProfileDto 实体,并且属性导致null 值,例如NullPointerException。):

          @RequestMapping(value = "/profile", method = RequestMethod.POST)
          public ProfileDto createProfile(@Valid @RequestBody ProfileDto profile){
              ...
          }
          

          这将在到达持久层之前返回您想要的状态代码 400 和一些带有org.springframework.web.bind.MethodArgumentNotValidException 的默认响应正文。 MethodArgumentNotValidException的处理在org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler中定义。

          这是另一个主题,但您可以选择通过创建 @ControllerAdvice@ExceptionHandler(MethodArgumentNotValidException.class) 来覆盖该行为,并根据您的需要自定义响应正文,因为默认错误响应正文不是最佳的,甚至不存在排除 ErrorMvcAutoConfiguration 时。

          警告:@ControllerAdvice 中的@ExceptionHandler(MethodArgumentNotValidException.class) 定位到将ResponseEntityExceptionHandler 结果扩展为IllegalStateException,因为在ResponseEntityExceptionHandler 中已经是为@987654343 定义的异常处理程序@。因此,只需将其放入另一个 @ControllerAdvice 类中,无需扩展任何内容。


          替代手动方法

          我看到您也可以手动触发电子邮件模式的验证(请参阅Manually call Spring Annotation Validation)。我自己没有测试过,但我个人不喜欢这种方法,因为它只会让你的控制器代码膨胀,而且我目前想不出需要它的用例。

          希望对遇到类似问题的人有所帮助。

          【讨论】:

            【解决方案5】:

            您可以通过在 @controllerAdvice 中添加它来处理 org.hibernate.exception.ConstraintViolationException

            @ExceptionHandler(DataIntegrityViolationException.class) public ResponseEntity handleConstraintViolationException(Exception ex){

                String errorMessage = ex.getMessage();
                errorMessage = (null == errorMessage) ? "Internal Server Error" : errorMessage;
            
                List<String> details = new ArrayList<>();
                 details.add(ex.getLocalizedMessage());
            
                return new ResponseEntity<ErrorResponseDTO>(
                        new ErrorResponseDTO( errorMessage ,details), HttpStatus.INTERNAL_SERVER_ERROR);
            
            }
            

            【讨论】:

              【解决方案6】:

              您无法捕获 ConstraintViolationException.class,因为它不会传播到代码的该层,而是被较低层捕获、包装并在另一种类型下重新抛出。因此,遇到 Web 层的异常不是 ConstraintViolationException。 所以你可以这样做:

              @ExceptionHandler({TransactionSystemException.class})
              protected ResponseEntity<Object> handlePersistenceException(final Exception ex, final WebRequest request) {
                  logger.info(ex.getClass().getName());
                  //
                  Throwable cause = ((TransactionSystemException) ex).getRootCause();
                  if (cause instanceof ConstraintViolationException) {        
              
                      ConstraintViolationException consEx= (ConstraintViolationException) cause;
                      final List<String> errors = new ArrayList<String>();
                      for (final ConstraintViolation<?> violation : consEx.getConstraintViolations()) {
                          errors.add(violation.getPropertyPath() + ": " + violation.getMessage());
                      }
              
                      final ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, consEx.getLocalizedMessage(), errors);
                      return new ResponseEntity<Object>(apiError, new HttpHeaders(), apiError.getStatus());
                  }
                  final ApiError apiError = new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, ex.getLocalizedMessage(), "error occurred");
                  return new ResponseEntity<Object>(apiError, new HttpHeaders(), apiError.getStatus());
              }
              

              【讨论】:

                【解决方案7】:
                @ResponseBody
                @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
                @ExceptionHandler(DataIntegrityViolationException.class)
                public Map errorHandler(DataIntegrityViolationException ex) {
                    Map map = new HashMap();
                    map.put("rs_code", 422);
                    map.put("rs_msg", "data existed !");
                    return map;
                }
                

                只要赶上org.springframework.dao.DataIntegrityViolationException

                【讨论】:

                  【解决方案8】:

                  只需检查所有例外并选择您需要的一项

                  1. 需要确定原因:

                    while ((cause = resultCause.getCause()) != null && resultCause != cause) {
                        resultCause = cause;
                    }
                    
                  2. 使用实例

                    @ExceptionHandler(Exception.class)
                    protected ResponseEntity<MyException> handleExceptions(Exception e) {
                        String message;
                        Throwable cause, resultCause = e;
                        while ((cause = resultCause.getCause()) != null && resultCause != cause) {
                            resultCause = cause;
                        }
                        if (resultCause instanceof ConstraintViolationException) {
                            message = (((ConstraintViolationException) resultCause).getConstraintViolations()).iterator().next().getMessage();
                        } else {
                            resultCause.printStackTrace();
                            message = "Unknown error";
                        }
                        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                                .body(new MyException(message));
                    }
                    

                  【讨论】:

                    【解决方案9】:

                    只是想添加一些东西。我试图做同样的事情,验证实体。然后我意识到,如果您验证控制器的输入,Spring 已经具备所有开箱即用的功能。

                    @RequestMapping(value = "/profile", method = RequestMethod.POST)
                    public ProfileDto createProfile(@Valid ProfileDto profile){
                    ...    
                    }
                    

                    @Valid 注释将触发使用 javax.validation 注释的验证。

                    假设您的个人资料用户名上有一个 Pattern 注释,其正则表达式不允许空格。

                    Spring 将构建一个状态为 400(错误请求)的响应和一个像这样的主体:

                    {
                        "timestamp": 1544453370570,
                        "status": 400,
                        "error": "Bad Request",
                        "errors": [
                            {
                                "codes": [
                                    "Pattern.ProfileDto.username",
                                    "Pattern.username",
                                    "Pattern.java.lang.String",
                                    "Pattern"
                                ],
                                "arguments": [
                                    {
                                        "codes": [
                                            "profileDto.username",
                                            "username"
                                        ],
                                        "arguments": null,
                                        "defaultMessage": "username",
                                        "code": "username"
                                    },
                                    [],
                                    {
                                        "defaultMessage": "^[A-Za-z0-9_\\-.]+$",
                                        "arguments": null,
                                        "codes": [
                                            "^[A-Za-z0-9_\\-.]+$"
                                        ]
                                    }
                                ],
                                "defaultMessage": "must match \"^[A-Za-z0-9_\\-.]+$\"",
                                "objectName": "profileDto",
                                "field": "username",
                                "rejectedValue": "Wr Ong",
                                "bindingFailure": false,
                                "code": "Pattern"
                            }
                        ],
                        "message": "Validation failed for object='profileDto'. Error count: 1",
                        "path": "/profile"
                    }
                    

                    【讨论】:

                      【解决方案10】:

                      我会仔细检查你是否导入了正确的ConstraintViolationException

                      你想要的来自org.hibernate.exception.ConstraintViolationException 包。如果您已导入 javax.validation.ConstraintViolationException,它将被跳过,就像您所经历的那样。

                      import org.hibernate.exception.ConstraintViolationException;
                      
                      @RestController
                      public class FeatureToggleController {
                      
                          @ExceptionHandler(ConstraintViolationException.class)
                          public ResponseEntity<Object> handleConstraintViolation(ConstraintViolationException ex, WebRequest request) {
                              return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
                          }
                      }
                      

                      这将按预期调用。

                      【讨论】:

                      • 使用 org.hibernate.exception.ConstraintViolationException 也不起作用。我也遇到了同样的问题,但是使用 org.hibernate.exception.ConstraintViolationException 并没有解决我的问题。
                      【解决方案11】:

                      您无法捕获ConstraintViolationException.class,因为它没有传播到代码的该层,它被较低层捕获,包装并重新抛出另一种类型。因此,访问您的 Web 层的异常不是ConstraintViolationException

                      就我而言,它是TransactionSystemException。 我正在使用来自 Spring 的 @Transactional 注释和 JpaTransactionManager。 EntityManager 在事务出现问题时抛出回滚异常,由JpaTransactionManager 转换为TransactionSystemException

                      所以你可以这样做:

                      @ExceptionHandler({ TransactionSystemException.class })
                      public ResponseEntity<RestResponseErrorMessage> handleConstraintViolation(Exception ex, WebRequest request) {
                          Throwable cause = ((TransactionSystemException) ex).getRootCause();
                          if (cause instanceof ConstraintViolationException) {
                              Set<ConstraintViolation<?>> constraintViolations = ((ConstraintViolationException) cause).getConstraintViolations();
                              // do something here
                          }
                      }
                      

                      【讨论】:

                      • 介意评论为什么答案被否决吗?我知道具体的例外情况可能会因您的特定版本的框架(spring boot/hibernate/...)而有所不同,但原则是相同的,应该适用于提出问题的人。
                      • 这是迄今为止最好的答案。我注意到他们在 Spring 版本 >= 2.4.0 中修复了这个问题,这意味着您现在可以这样做:@ExceptionHandler(ConstraintViolationException.class) 并且您现在也可以处理所有其他嵌套异常。
                      • @nimai 原谅他们;因为他们不知道他们在做什么。在这里,投赞成票。
                      【解决方案12】:

                      我认为您应该将@ResponseStatus(HttpStatus.BAD_REQUEST) 添加到您的@ExceptionHandler

                      @ExceptionHandler(ConstraintViolationException.class)
                      @ResponseStatus(HttpStatus.BAD_REQUEST)
                      public ResponseEntity<Object> handleConstraintViolation(ConstraintViolationException ex, WebRequest request) {
                          List<String> errors = new ArrayList<String>();
                          ....
                      }
                      

                      【讨论】:

                      • 没用。无论出于何种原因,Spring 都没有看到方法 handleConstraintViolation()。同一个类中的另一个方法 (handleMethodArgumentNotValid()) 会按原样执行 - 但不是这个方法。
                      • 这不适用于某些版本的 SpringBoot。但它适用于 Spring Boot 版本 >= 2.4.0
                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 2019-08-06
                      • 1970-01-01
                      • 2017-09-30
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2017-05-02
                      相关资源
                      最近更新 更多