【问题标题】:Spring Data Rest - Bean validation is not applied to PUT method?Spring Data Rest - Bean 验证不适用于 PUT 方法?
【发布时间】:2017-05-11 13:47:30
【问题描述】:

我有一个定义如下的域类

@Data
@Entity
public class City {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private long cityID;

    @NotBlank(message = "City name is a required field")
    private String cityName;


}

当我在没有 cityName 的情况下向端点 http://localhost:8080/cities 发帖时,我会收到 ConstraintViolationException,但是当我向没有 cityName 的端点 http://localhost:8080/cities/1 发送 PUT 请求时,我会收到以下异常而不是 ConstraintViolationException。

{
  "timestamp": 1494510208982,
  "status": 500,
  "error": "Internal Server Error",
  "exception": "org.springframework.transaction.TransactionSystemException",
  "message": "Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction",
  "path": "/cities/1"
}

那么如何为 PUT 请求获取 ConstraintViolationException 异常?

注意:我使用的是 Spring Data Rest,因此端点是由 Spring 生成的。没有自定义的休息控制器。

【问题讨论】:

  • 可以分享restcontroller类吗?
  • 我正在使用 Spring Data Rest,因此 Spring 在运行时生成端点,因此无需实现自定义控制器
  • @pvpkiran 链接的问题是关于发送一个未知属性,但我正在为 cityName 发送一个空值,所以这个问题没有帮助
  • 在我的 SDR 项目(spring-boot 1.4.3)中,验证按预期工作。字段定义示例:@NotNull(message = "valid.field")@Pattern(regexp = NAME_PATTERN, message = "valid.username")@Column(nullable = false)private String name;

标签: java spring rest spring-boot spring-data-rest


【解决方案1】:

我认为 Cepr0 的测试适用于 PUT 和 POST,因为当您为不存在的实体发送 PUT 请求时,Spring Data Rest 会在后台使用 create 方法。 假设没有 id=100 的用户: 调用“PUT users/100”与调用“POST users/”是一样的

当您为现有实体发送 PUT 时,它会生成讨厌的 TransactionSystemException。

我现在也在与 Data Rest 异常处理作斗争,其中有很多不一致之处。

这是我目前的 RestErrorAttributes 课程,它解决了我的大部分问题,但我很有可能在接下来的几天里会喜欢其他人。 :)

@Component
@Slf4j
public class RestErrorAttributes extends DefaultErrorAttributes implements MessageSourceAware {

private MessageSource messageSource;

@Override
public void setMessageSource(MessageSource messageSource) {
    this.messageSource = messageSource;
}

/** {@inheritDoc} */
@Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {

    final Map<String, Object> errorAttributes = super.getErrorAttributes(requestAttributes, includeStackTrace);
    // Translate default message by Locale
    String message = errorAttributes.get("message").toString();
    errorAttributes.put("message",
            messageSource.getMessage(message, null, message, LocaleContextHolder.getLocale()));
    // Extend default error message by field-errors
    addConstraintViolationDetails(errorAttributes, requestAttributes);
    return errorAttributes;
}

private void addConstraintViolationDetails(Map<String, Object> errorAttributes,
        RequestAttributes requestAttributes) {
    Throwable error = getError(requestAttributes);
    if (error instanceof ConstraintViolationException) {
        errorAttributes.put("errors",
                RestFieldError.getErrors(((ConstraintViolationException) error).getConstraintViolations()));
    }
    else if (error instanceof RepositoryConstraintViolationException) {
        errorAttributes.put("errors", RestFieldError
                .getErrors(((RepositoryConstraintViolationException) error).getErrors().getAllErrors()));
    }
}

/** {@inheritDoc} */
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
        Exception ex) {

    try {
        Throwable cause = ex;
        while (cause instanceof Exception) {
            // Handle AccessDeniedException - It cannot be handled by
            // ExceptionHandler
            if (cause instanceof AccessDeniedException) {
                response.sendError(HttpStatus.FORBIDDEN.value(), cause.getMessage());
                super.resolveException(request, response, handler, (Exception) cause);
                return new ModelAndView();
            }
            // Handle exceptions from javax validations
            if (cause instanceof ConstraintViolationException) {
                response.sendError(HttpStatus.UNPROCESSABLE_ENTITY.value(), "validation.error");
                super.resolveException(request, response, handler, (Exception) cause);
                return new ModelAndView();
            }
            // Handle exceptions from REST validator classes
            if (cause instanceof RepositoryConstraintViolationException) {
                response.sendError(HttpStatus.UNPROCESSABLE_ENTITY.value(), "validation.error");
                super.resolveException(request, response, handler, (Exception) cause);
                return new ModelAndView();
            }
            cause = ((Exception) cause).getCause();
        }
    } catch (final Exception handlerException) {
        log.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException);
    }
    return super.resolveException(request, response, handler, ex);
}

@Getter
@AllArgsConstructor
public static class RestFieldError {
    private String field;
    private String code;
    private String message;

    public static List<RestFieldError> getErrors(Set<ConstraintViolation<?>> constraintViolations) {
        return constraintViolations.stream().map(RestFieldError::of).collect(Collectors.toList());
    }

    public static List<RestFieldError> getErrors(List<ObjectError> errors) {
        return errors.stream().map(RestFieldError::of).collect(Collectors.toList());
    }

    private static RestFieldError of(ConstraintViolation<?> constraintViolation) {
        return new RestFieldError(constraintViolation.getPropertyPath().toString(),
                constraintViolation.getMessageTemplate(), constraintViolation.getMessage());

    }

    private static RestFieldError of(ObjectError error) {

        return new RestFieldError(error instanceof FieldError ? ((FieldError) error).getField() : null,
                error.getCode(), error.getDefaultMessage());

    }
}

}

【讨论】:

    【解决方案2】:

    我的解决方法是设置一个异常处理程序来处理TransactionSystemException,解开异常并像普通的ConstraintViolationException 一样处理:

    @ExceptionHandler(value = {TransactionSystemException.class})
    public ResponseEntity handleTxException(TransactionSystemException ex) {
        Throwable t = ex.getCause();
        if (t.getCause() instanceof ConstraintViolationException) {
            return handleConstraintViolation((ConstraintViolationException) t.getCause(), null);
        } else {
            return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
    
    @ExceptionHandler({ConstraintViolationException.class})
    public ResponseEntity<Object> handleConstraintViolation(ConstraintViolationException ex,
              WebRequest request) {
        List<String> errors = new ArrayList<>();
        for (ConstraintViolation<?> violation : ex.getConstraintViolations()) {
            errors.add(violation.getRootBeanClass().getName() + " " + violation.getPropertyPath() + ": " + violation.getMessage());
        }
    
        return new ResponseEntity<>(errors, new HttpHeaders(), HttpStatus.CONFLICT);
    }
    

    【讨论】:

      猜你喜欢
      • 2016-03-02
      • 2019-05-22
      • 2013-08-21
      • 2019-01-14
      • 2017-10-22
      • 1970-01-01
      • 2017-03-06
      • 2016-04-24
      • 1970-01-01
      相关资源
      最近更新 更多