Spring 数据剩余:预期 4xx 时出现 500 错误

Spring Data Rest: 500 error when 4xx expected

给定以下实体,当我 "post" "TrainerProfile" 的新实体并错过 "Location" 中的一些 @NotNull 参数时,我得到 500 和堆栈跟踪打包到 JSON 而不是 400 和关于哪里出了问题的有用信息。

@Entity
public class TrainerProfile {
    ...

    @NotNull
    @OneToOne(cascade = CascadeType.ALL)
    private Location location;

}

@Entity
public class Location {

    @Id @GeneratedValue
    private Long id;

    @NotNull
    private String zipcode;

    @NotNull
    private String city;

    @NotNull
    private String country;

}

当我post这个数据到API

{
  ...
  "location": {
    "zipcode": "10000"
  }
}

我看到以下日志:

javax.validation.ConstraintViolationException: Validation failed for classes [training.edit.provider.model.Location] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
    ConstraintViolationImpl{interpolatedMessage='must not be null', propertyPath=city, rootBeanClass=class training.edit.provider.model.Location, messageTemplate='{javax.validation.constraints.NotNull.message}'}
    ConstraintViolationImpl{interpolatedMessage='must not be null', propertyPath=country, rootBeanClass=class training.edit.provider.model.Location, messageTemplate='{javax.validation.constraints.NotNull.message}'}
]

但是 REST 客户端看到这个:

< HTTP/1.1 500
< Vary: Origin
< Vary: Access-Control-Request-Method
< Vary: Access-Control-Request-Headers
< Access-Control-Allow-Origin: http://localhost:4200
< Access-Control-Allow-Credentials: true
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Frame-Options: DENY
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Tue, 16 Jun 2020 09:33:16 GMT
< Connection: close
<
{"timestamp":"2020-06-16T09:33:16.605+0000","status":500,"error":"Internal Server Error","message":"Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction","trace":"org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction\n\tat org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:543)\n...

文字有点长,剩下的我给你。

我这样配置验证:

@Configuration
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class RestConfiguration implements RepositoryRestConfigurer {

    private final @NonNull Validator validator;

    private final @NonNull UriToIdConverter converter;

    @Override
    public void configureConversionService(ConfigurableConversionService conversionService) {
        RepositoryRestConfigurer.super.configureConversionService(conversionService);
        conversionService.addConverter(converter);
    }

    @Override
    public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
       validatingListener.addValidator("afterCreate", validator);
       validatingListener.addValidator("beforeCreate", validator);
       validatingListener.addValidator("afterSave", validator);
       validatingListener.addValidator("beforeSave", validator);
    }

}

在这种情况下如何获得正确的错误消息?当我 post 某些不适用于 "TrainerProfile" 的内容时,我会收到正确的消息和错误代码,但不适用于嵌套对象。

中间解决方案

每个 Spring 数据 REST 请求发出一个事务。外部异常 RollbackException 给你一个 500 响应,即使这个异常实际上来自嵌套验证失败 ConstraintViolationException。一个有效但不是很好的解决方案应该有一个 ResponseEntityExceptionHandler 来提取嵌套异常,如 auth0 example. The code is here.

建议

Just don't use Spring Data REST where it doesn't fit your requirements.

正如 Spring Data REST 负责人 Oliver Drotbohm 在这篇 answer.

中所说

A RESTful API 应该适用于 聚合 聚合 是一个 DDD 概念。聚合不适用于关系数据库。尝试 No-SQL 数据库,例如 Mongo DB。学习DDD的起点是Oliver's talk.