SpringBoot不处理javax.validation.ConstraintViolationException

时间:2017-07-13 02:50:52

标签: java spring-boot jpa-2.0

我已经在我的Entity类中定义了验证电子邮件的模式。在我的验证异常处理程序类中,我为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)    

12 个答案:

答案 0 :(得分:13)

你无法捕捉ConstraintViolationException.class,因为它没有传播到你的代码层,它被较低层捕获,包裹并在另一种类型下重新抛出。因此,点击您的网络图层的例外不是ConstraintViolationException

在我的情况下,它是TransactionSystemException。 我正在使用@Transactional来自Spring的JpaTransactionManager注释。当事务中的某些事情出错时,EntityManager会抛出一个回滚异常,由TransactionSystemException转换为JpaTransactionManager

所以你可以这样做:

@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
    }
}

答案 1 :(得分:2)

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

为了澄清问题……nimai已经正确提及:

您不能捕获ConstraintViolationException.class,因为它没有传播到代码的这一层,它被较低的层捕获,并包装在另一种类型下。因此,到达您的Web层的异常不是ConstraintViolationException

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


解决方案

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

NullPointerException

这将返回您想要的状态代码400和一些默认响应正文,并带有@RequestMapping(value = "/profile", method = RequestMethod.POST) public ProfileDto createProfile(@Valid @RequestBody ProfileDto profile){ ... } ,甚至到达持久层之前。 org.springframework.web.bind.MethodArgumentNotValidException的处理在MethodArgumentNotValidException中定义。

这是另一个主题,但是您可以选择通过使用org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler创建一个@ControllerAdvice来覆盖该行为,并根据需要定制响应主体,因为默认错误响应主体不是最佳的并且在排除ErrorMvcAutoConfiguration时甚至不存在。

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


替代人工方法

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

我希望这可以帮助其他人遇到类似的问题。

答案 2 :(得分:1)

那是我的解决方法...

@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() {
    }
...
}

答案 3 :(得分:0)

我认为您应该将@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>();
    ....
}

答案 4 :(得分:0)

@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

答案 5 :(得分:0)

只想添加一些内容。我试图做同样的事情,验证实体。然后我意识到,如果您验证控制器的输入,Spring便会提供开箱即用的功能。

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

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

假设您的个人资料用户名上带有“模式”注释,并且正则表达式不允许使用空格。

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"
}

答案 6 :(得分:0)

只需检查所有例外,然后选择所需的例外

  1. 需要确定原因:

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

    @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));
    }
    

答案 7 :(得分:0)

您不能捕获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());
}

答案 8 :(得分:0)

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

@ExceptionHandler(DataIntegrityViolationException.class)     公共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);

}

答案 9 :(得分:0)

尝试这种方式。

@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);

    }
}

答案 10 :(得分:0)

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

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));
            }

        }

答案 11 :(得分:-1)

我会仔细检查您是否导入了正确的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);
    }
}

将按预期方式调用。

相关问题