SpringBoot doesn't handle org.hibernate.exception.ConstraintViolationException

后端 未结 11 932
没有蜡笔的小新
没有蜡笔的小新 2020-12-10 01:37

I have defined a pattern for validating email in my Entity class. In my validation exception handler class, I have added handler for ConstraintViolationException. My appli

相关标签:
11条回答
  • 2020-12-10 02:13

    You cannot catch ConstraintViolationException.class because it's not propagated to that layer of your code, it's caught by the lower layers, wrapped and rethrown under another type. So that the exception that hits your web layer is not a ConstraintViolationException.

    In my case, it's a TransactionSystemException. I'm using @Transactional annotations from Spring with the JpaTransactionManager. The EntityManager throws a rollback exception when somethings goes wrong in the transaction, which is converted to a TransactionSystemException by the JpaTransactionManager.

    So you could do something like this:

    @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
        }
    }
    
    0 讨论(0)
  • 2020-12-10 02:16

    I think you should add @ResponseStatus(HttpStatus.BAD_REQUEST) to your @ExceptionHandler:

    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseEntity<Object> handleConstraintViolation(ConstraintViolationException ex, WebRequest request) {
        List<String> errors = new ArrayList<String>();
        ....
    }
    
    0 讨论(0)
  • 2020-12-10 02:21

    Following solution is based on Spring Boot 2.1.2.

    To clarify things... as nimai already correctly mentioned:

    You cannot catch ConstraintViolationException.class because it's not propagated to that layer of your code, it's caught by the lower layers, wrapped and rethrown under another type. So that the exception that hits your web layer is not a ConstraintViolationException.

    In your case it is probably a DataIntegrityViolationException, which points out a problem in the persistence layer. But you don't want to let it come that far.


    Solution

    Make use of the @Valid annotation for the entity given as method parameter as Ena mentioned. On my version it was missing the org.springframework.web.bind.annotation.RequestBody annotation (Without the @RequestBody annotation the ProfileDto cannot be parsed correctly into your ProfileDto entity and the properties are resulting in null values, e.g. NullPointerException.):

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

    This will then return your wanted status code 400 and some default response body accompanied by a org.springframework.web.bind.MethodArgumentNotValidException before even reaching the persistence layer. The processing of the MethodArgumentNotValidException is defined in org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler.

    This is another topic, but you then have the option to override that behaviour by creating a @ControllerAdvice with @ExceptionHandler(MethodArgumentNotValidException.class) and customize the response body to your needs, since the default error response body is not optimal and not even present when excluding ErrorMvcAutoConfiguration.

    Caution: Locating the @ExceptionHandler(MethodArgumentNotValidException.class) inside the @ControllerAdvice that extends the ResponseEntityExceptionHandler results into an IllegalStateException, because in the ResponseEntityExceptionHandler already is an exception handler defined for MethodArgumentNotValidException. So just put it into another @ControllerAdvice class without extending anything.


    Alternative manual approach

    I saw you can also trigger the validation of the email pattern manually (see Manually call Spring Annotation Validation). I didn't test it myself, but I personally don't like that approach, because it is just bloating your controller code and I currently can't think of a use case that requires it.

    I hope that helps others encountering a similar issue.

    0 讨论(0)
  • 2020-12-10 02:22

    You cannot catch ConstraintViolationException.class because it's not propagated to that layer of your code, it's caught by the lower layers, wrapped and rethrown under another type. So that the exception that hits your web layer is not a ConstraintViolationException. So you could do something like this:

    @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());
    }
    
    0 讨论(0)
  • 2020-12-10 02:29

    Try this way..

    @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);
    
        }
    }
    
    
    0 讨论(0)
提交回复
热议问题