Я обновил проект с Spring 4.3.9.RELEASE + Hibernate 4.3.11.Final до Spring Boot 2.1.4.RELEASE и Hibernate 5.3.9.Final. Запросы все еще работают нормально, но я получаю LazyInitializationException с некоторыми @OneToMany членами класса.
Сначала я извлекаю объект, который имеет ссылку на список @OneToMany, из службы @Transaction. Коллекция возвращается в контроллер, а оттуда возвращается в Spring для сериализации в json. У контроллера есть @RestController, так что он знает, что делать.
В Spring 4.3.9.RELEASE + Hibernate 4.3.11.Final все было хорошо, хотя OpenEntityManagerInView не был включен в конфигурации и коллекция не загружалась в режиме EAGER. Но в Spring Boot 2.1.4.RELEASE и Hibernate 5.3.9.Final то же самое больше не работает. Я пытался включить OEMIV, установив spring.jpa.open-in-view=true, но даже это не работает или где-то переопределяется.
Если я включу режим загрузки EAGER для этой коллекции, все будет работать нормально.
пом.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 довольно мал, поэтому здесь я застрял. Возможно, дело в настройках, но я не могу найти.
Это не первое обновление Spring Boot, так что ничего особенного. Hibernate 5.3.9.Final поставляется с Spring Boot, поэтому я не стал переходить на предыдущую версию. Я учту это, спасибо. Что касается кода, я не добавлял его, потому что его легко объяснить. Сейчас сделаю некоторые модификации.
Вы используете простой спящий режим, а НЕ jpa, поэтому включение OpenEntityManagerInView, конечно, не поможет. Судя по вашему вопросу, вы переходите от обычного приложения Spring к Spring Boot.
Да, это правильно, я перехожу на Spring Boot. Я делал это раньше, но у меня не был включен Hibernate.
Только что сделал тест с Hibernate 5.0.3.Final, результат тот же. Что касается конфигурации, я ничего не делал, кроме как переместил ее из файла конфигурации в код.
Вы переходите с hibernate4 на 5, убедитесь, что вы используете правильную инфраструктуру. Также, как уже упоминалось, вы используете Hibernate NOT простой JPA.
Что вы подразумеваете под правильной инфраструктурой? Все работает нормально, единственная проблема это ленивая загрузка.
Правильные классы Spring для Hibernate 5 и, как уже дважды упоминалось, материал OpenEntituyManager не поможет, поскольку вы используете простой спящий режим, а НЕ jpa. Вместо этого вам нужен OpenSessionInView. Однако я настоятельно рекомендую отказаться от простого Hibernate в пользу JPA. Кроме того, как уже говорилось, вы переходите от простого приложения Spring к Spring BOot, поэтому, вероятно, есть некоторые дополнительные фильтры в файле web.xml или WebApplicationInitializer, используемые для начальной загрузки приложения, которые вы не настроили/не скопировали.
Я попытаюсь применить это снова. Приложение отлично работает после перехода на Spring Boot. Запросы к базе данных также работают очень хорошо. В предыдущей версии использовался Hibernate, теперь по-прежнему Hibernate. Единственная проблема связана с коллекциями EAGER, но я не понимаю, как это работает с Hibernate 4.
Это не имеет ничего общего с спящей версией. Но с другими компонентами, настроенными в простом приложении Spring, а не в приложении Spring Boot. Как уже говорилось, я подозреваю, что в вашем приложении Spring были настроены OpenSessionInViewFilter или OpenSessionInViewInterceptor. Вы не перенесли это на Spring Boot, и, следовательно, это не удается.
В обновлениях Hibernate есть только одна константа: с каждым обновлением вы узнаете новые вещи, которые всегда делали неправильно. Старые версии Hibernate были довольно снисходительны, новые версии довольно строги.
Вы были правы, наконец мне удалось проверить, что OpenSessionInViewFilter был настроен и не настроен в Spring Boot.
@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 в свои коллекции.
Я знаю, что вы имеете в виду, но идентичный код отлично работает в Spring 4.3.9.RELEASE + Hibernate 4.3.11.Final.
Страстное извлечение коллекций oneToMany не совсем хорошая идея, есть веская причина, по которой они ленивы по умолчанию. Это вполне может вызвать бомбардировку запросов в зависимости от того, насколько велика коллекция. Однако объединение выборки является хорошим предложением по производительности.
На данный момент времени на переработку всего слоя репозитория недостаточно, поэтому приходится придерживаться того, что дано. В будущем оптимизация Hibernate и интеграция — следующий логический шаг.
@Gimby Согласитесь, тип выборки EAGER - не очень хорошая идея. Инициализация отношений, когда вам это нужно, гораздо лучше. Я описал это в своем ответе.
Как говорил М. Дейнум, конфигурация 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.
Спасибо М. Дейнум.
Итак, вы обновляетесь с 4.3 (Spring) до 5.1 (2 основные версии), в спящем режиме вы переходите на 4 версии, переходите из приложения Spring в приложение Spring Boot. Не слишком ли много вы делаете за один раз? Вы используете спящий режим, но используете ли вы JPA или обычный спящий режим. Вы не показываете код, который не работает, и конфигурацию.