@ Валидация графа объекта не работает с JPA и TraversableResolver - PullRequest
1 голос
/ 01 октября 2019

Я пытаюсь вручную проверить граф объектов. У меня не было проблем с этим при использовании Hibernate SessionFactory. Поскольку я перешел на Hibernate JPA, вложенные сущности больше не проверяются. Почему?

  • Проверка на основе событий Hibernate выполняется с группой по умолчанию и работает на этапах 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);
}

Смежные вопросы:

1 Ответ

1 голос
/ 02 октября 2019

Я нашел причину. Отношение «родитель-потомок» объявляется с FetchType.EAGER, но в моем коде родительский объект уже лениво загружен до достижения шага проверки. Так как Hibernate уже кешировал объект, он будет получать прокси, а не загруженный экземпляр.

От этот пост .

Hibernate делает все возможное, чтобыиметь один и только один экземпляр объекта в сеансе.

Родительский объект уже был загружен из другого отношения.

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

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

Но это еще не все. Это не ленивая загрузка ванили, иначе TraversableResolver решил бы проблемы и не объясняет, почему встроенные дочерние элементы также не могут быть проверены.

Глядя на отладчик, кажется, что это какой-то байтулучшение кода сделано на родительском объекте. Каждое поле отображается как пустое, но к нему можно получить доступ по запросу (при условии, что запрос не проверяется компонентом).
byte code enhancement

Сущность не просто прокси, нотакже изменены в некотором роде.

  • Отношения не могут быть пройдены даже с использованием TraversableResolver только для да.
  • Встроенные объекты игнорируются валидатором.

Решениене использовать Lombok, а объявлять получатели и комментировать их с помощью @Valid.

Когда предполагается, что проверенные ленивые ассоциации должны быть проверены, рекомендуется поместить ограничение на получатель ассоциации.

См. Документацию Bean Validation для цитаты и этот пост для дальнейших ссылок.

Я создал проблему в трекере Hibernate и открыл второй вопрос SO запрашивая больше объяснений.

...