【问题标题】:@Valid object graph validation not working with JPA and TraversableResolver@Valid 对象图验证不适用于 JPA 和 TraversableResolver
【发布时间】:2019-10-01 11:44:29
【问题描述】:

我尝试手动验证实体图。使用 Hibernate SessionFactory 时我没有遇到问题。由于我切换到 Hibernate JPA,因此不再验证嵌套实体。为什么?

  • Hibernate event-based validation 使用默认组运行,并在 pre-persiste/pre-update/pre-remove 阶段工作,但手动验证不会检测嵌套实体中的验证错误。
  • 整个实体图都被急切地加载,所以我认为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);
}

相关问题:

【问题讨论】:

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


    【解决方案1】:

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

    来自this post

    Hibernate 尽其所能拥有一个且只有一个 会话中的实体。

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

    @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 验证)。

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

    • 即使使用 yes-only TraversableResolver 也无法遍历关系。
    • 嵌入的对象被验证器忽略。

    解决方案是不使用 Lombok,而是声明 getter 并使用 @Valid 注释它们。

    当延迟加载的关联应该被验证时,它是 建议将约束放在关联的getter上。

    请参阅Bean Validation documentation 以获取报价,this post 以获取更多参考资料。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-11-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-02
      • 2011-07-05
      • 2021-12-31
      相关资源
      最近更新 更多