Обновление Hibernate 4 -> 5 приводит к исключению LazyInitializationException - PullRequest
0 голосов
/ 09 мая 2019

Я обновил проект с Spring 4.3.9.RELEASE + Hibernate 4.3.11. Окончательная версия до Spring Boot 2.1.4.RELEASE и Hibernate 5.3.9.Final. Запросы все еще работают нормально, но я получаю LazyInitializationException с некоторыми @OneToMany членами класса.

Сначала я извлекаю объект, имеющий ссылку на список @OneToMany, из службы @Transaction. Коллекция возвращается контроллеру и оттуда возвращается в Spring для сериализации в json. Контроллер имеет @RestController, поэтому он знает, что делать.

Весной 4.3.9.RELEASE + Hibernate 4.3.11. Наконец-то все было хорошо, хотя OpenEntityManagerInView не был включен конфигурацией и коллекция не была загружена в режиме EAGER. Но в Spring Boot 2.1.4.RELEASE и Hibernate 5.3.9.Final то же самое больше не работает. Я попытался включить OEMIV, установив spring.jpa.open-in-view=true, но даже это, похоже, не работает или где-то переопределяется.

Если я включаю режим загрузки EAGER для этой коллекции, все работает нормально.

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

@Entity
@JsonSerialize(using = TemplateSerializer.class)
public class Template implements Serializable {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @ManyToOne
    private ObjFormat objFormat;

    @OneToOne
    @JoinColumn(name = "event_id")
    @OnDelete(action = OnDeleteAction.CASCADE)
    private Event event;

    @OneToMany
    @JoinColumn(name = "category_id")
    private List<Category> linkToCategories;

Проблема вызвана полем linkToCategories. Если я настрою @OneToMany (fetch = FetchType.EAGER), все будет нормально.

Конфигурация приложения:

    @Bean
    public LocalSessionFactoryBean sessionFactory(DataSource dataSource) throws ClassNotFoundException {
        LocalSessionFactoryBean localSessionFactoryBean = new LocalSessionFactoryBean();
        localSessionFactoryBean.setDataSource(dataSource);
              localSessionFactoryBean.setPackagesToScan("com.project.backend.model",
            "com.project.backend.hibernate.converters");
        return localSessionFactoryBean;
    }

    @Bean
    public HibernateTransactionManager transactionManager(SessionFactory sessionFactory) {
        return new HibernateTransactionManager(sessionFactory);
    }

Позже редактировать: После многих отладок разница между старой и новой функциональностью Hibernate заключается в HibernateTransactionManager. В методе doGetTransaction () в Hibernate 4 он находит объект SessionHolder при вызове

TransactionSynchronizationManager.getResource (getSessionFactory ())

в то время как в Hibernate 5 это не так.

    SessionHolder sessionHolder =
            (SessionHolder) TransactionSynchronizationManager.getResource(getSessionFactory());
    if (sessionHolder != null) {
        if (logger.isDebugEnabled()) {
            logger.debug("Found thread-bound Session [" + sessionHolder.getSession() + "] for Hibernate transaction");
        }
        txObject.setSessionHolder(sessionHolder);
    }
    else if (this.hibernateManagedSession) {
        try {
            Session session = this.sessionFactory.getCurrentSession();
            if (logger.isDebugEnabled()) {
                logger.debug("Found Hibernate-managed Session [" + session + "] for Spring-managed transaction");
            }
            txObject.setExistingSession(session);
        }
        catch (HibernateException ex) {
            throw new DataAccessResourceFailureException(
                    "Could not obtain Hibernate-managed Session for Spring-managed transaction", ex);
        }
    }

В методе doBegin новый сеанс создается и устанавливается для txObject для каждого запроса.

        if (txObject.getSessionHolder() == null || txObject.getSessionHolder().isSynchronizedWithTransaction()) {
            Interceptor entityInterceptor = getEntityInterceptor();
            Session newSession = (entityInterceptor != null ?
                    getSessionFactory().withOptions().interceptor(entityInterceptor).openSession() :
                    getSessionFactory().openSession());
            if (logger.isDebugEnabled()) {
                logger.debug("Opened new Session [" + newSession + "] for Hibernate transaction");
            }
            txObject.setSession(newSession);
        }

Мой опыт работы с Hibernate довольно небольшой, поэтому я застрял. Это, вероятно, вещь конфигурации, но я не могу ее найти.

Ответы [ 2 ]

1 голос
/ 14 мая 2019

Как говорил М. Дейнм, конфигурация Spring 4.3.9.RELEASE + Hibernate 4.3.11.Final загружала OpenSessionInViewFilter, что объясняет, почему все запросы проходили успешно.После настройки того же фильтра в Spring Boot все возвращается в нормальное состояние.Добавьте следующий bean-компонент для регистрации фильтра:

@Bean
public FilterRegistrationBean<OpenSessionInViewFilter> registerOpenSessionInViewFilterBean() {
    FilterRegistrationBean<OpenSessionInViewFilter> registrationBean = new FilterRegistrationBean<>();
    OpenSessionInViewFilter filter = new OpenSessionInViewFilter();
    registrationBean.setFilter(filter);
    return registrationBean;
}

Следующим шагом является замена обычного Hibernate на JPA, а OpenSessionInViewFilter на OpenEntityManagerInViewFilter.

Спасибо, М. Deinum.

0 голосов
/ 09 мая 2019

@xxxToMany аннотации указывают, что тип выборки LAZY по умолчанию. Это означает, что вам нужно инициализировать коллекцию, на которую ссылается ваша сущность.

Например.

@Entity
public class Book {
    @OneToMany
    public List<Author> authors;    

}

Есть несколько способов решить эту проблему. Вы можете изменить @OneToMany аннотацию с помощью:

@OneToMany(FetcType=FetchType.EAGER)

Или создать метод, в котором вы будете инициализировать authors Например .:

public void initializeAuthors(Book book) {

    Book b = em.find(Book.class, book.getId());
    List<Author> authors = new ArrayList<>(b.getAuthors());

    book.setAuthors(authors);

}

Если у вас есть @NamedQueries в ваших сущностях, вы можете сделать это, добавив LEFT JOIN FETCH в свои коллекции.

...