java - @Valid 对象图验证不适用于 JPA 和 TraversableResolver

标签 java hibernate spring-boot jpa spring-data-jpa

我尝试手动验证实体图。使用 Hibernate SessionFactory 时我没有遇到任何问题。自从我切换到 Hibernate JPA 后,嵌套实体不再被验证。为什么?

  • Hibernate event-based validation使用默认组运行,并在预持久/预更新/预删除阶段工作,但手动验证不会检测嵌套实体中的验证错误。
  • 整个实体图已急切加载,因此我假设 TraversableResolver不是这里的问题。不管怎样,我仍然声明了一个自定义的 TraversableResolver ,它总是请求导航到嵌套实体。
  • 如果我在单元测试中在持久性上下文之外创建一个新的实体图,则会发现验证错误。然而,如果我将父实体与持久性上下文分离,仍然找不到验证错误。

如果您能帮助理解这个问题,我们将不胜感激。

我正在使用 org.springframework.boot:spring-boot-starter-data-jpa (Spring Boot 2.1.7.RELEASE),打包为 Hibernate 5.3.10.Final 。我也在使用 Lombok。

这是我的代码。如果父类中存在 alwaysAnError 字段,则会发现验证错误。如果此字段嵌套在 @Valid 子项中,则不会发现错误。

Parent.java

@Data @Builder @NoArgsConstructor @AllArgsConstructor
@Entity
@Table(name = "parent")
public class Parent {  
    [...]  

    @Valid
    @Builder.Default
    @OneToMany(mappedBy = "file", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
    @Fetch(value = FetchMode.SUBSELECT)
    private List<@Valid Child> children = new ArrayList<>();
}

Child.java

@Data @Builder @NoArgsConstructor @AllArgsConstructor
@Entity
@Table(name = "child")
public class Child {
    [...]  

    @ManyToOne
    @JoinColumn(name = "file_id")
    private File file;

    @NotNull
    @Transient
    private String alwaysAnError = null; 
}

ValidationService.java

Validator validator = Validation.byDefaultProvider()
    .configure()
    .traversableResolver(new TraversableResolver() {
        @Override
        public boolean isReachable(Object traversableObject, Path.Node traversableProperty, Class<?> rootBeanType, Path pathToTraversableObject, ElementType elementType) {
            return true;
        }

       @Override
       public boolean isCascadable(Object traversableObject, Path.Node traversableProperty, Class<?> rootBeanType, Path pathToTraversableObject, ElementType elementType) {
           return true;
       }
    })
    .buildValidatorFactory()
    .getValidator();

Set<ConstraintViolation<Declaration>> constraintViolations = validator.validate(fileInstance, Default.class);
if (!constraintViolations.isEmpty()) {
    throw new RuntimeException(constraintViolations);
}

相关问题:

最佳答案

我找到原因了。父子关系是用 FetchType.EAGER 声明的,但在我的代码中,父实体在到达验证步骤之前已经被延迟加载。由于 Hibernate 已经缓存了实体,因此它将检索代理而不是急切加载的实例。

来自this post .

Hibernate does everything it can to have one and only one instance of an entity in the session.

父实体已从另一个关系加载。

@Entity
@Table(name = "lazy_child")
public class LazyChild {
    [...]  

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "file_id")
    private File file;
}

但这还不是全部。这不是普通的延迟加载,否则 TraversableResolver 会解决问题,但它也不能解释为什么嵌入的子项无法被验证。

查看调试器,似乎是某种 byte code enhancement是在父实体上进行的。 每个字段都显示为 null,但可以根据请求进行访问(只要不是执行请求的 Bean 验证)。
byte code enhancement

实体不仅被代理,而且还以某种方式进行修改。

  • 即使使用仅限 yes 的 TraversableResolver 也无法遍历关系。
  • validator 会忽略嵌入的对象。

解决方案是不使用 Lombok,而是声明 getter 并使用 @Valid 对其进行注释。

When lazy loaded associations are supposed to be validated it is recommended to place the constraint on the getter of the association.

请参阅Bean Validation documentation报价和this post供进一步引用。

我创建了一个issue in the Hibernate tracker并打开a second SO question请求更多解释。

关于java - @Valid 对象图验证不适用于 JPA 和 TraversableResolver,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58184136/

相关文章:

Java 8 单元测试 CompletableFuture 异常

java - Hibernate 的 load() 方法对不存在的 ID 做了什么?

java - 在测试中使用@SpringApplicationConfiguration会抛出异常?

java - JavaFX ComboBox API 中的 NullPointerException

java - 简单的 NetBeans servlet,按钮不起作用

使用 Websphere 进行 java.util.logging

java - Hibernate xml 列定义

java - JPA+SQLite问题

java - Spring 从实体访问属性的方式是什么?

spring - 多次引用数据库序列 [hibernate_sequence]