Hibernate 4 -> 5 升级导致 LazyInitializationException
Hibernate 4 -> 5 upgrade leads to LazyInitializationException
我将项目从 Spring 4.3.9.RELEASE + Hibernate 4.3.11.Final 升级到 Spring Boot 2.1.4.RELEASE 和 Hibernate 5.3.9.Final。查询仍然工作正常,但我得到 LazyInitializationException
一些 @OneToMany
class 成员。
首先,我从 @Transaction
服务检索对象,该对象引用了 @OneToMany
列表。该集合返回到控制器,然后从那里返回到 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 中,同样的事情不再起作用了。我已经尝试通过设置 spring.jpa.open-in-view=true
来启用 OEMIV,但即使这样似乎也不起作用或者它在某处被覆盖了。
如果我为该集合启用 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 的经验很少,所以我被卡住了。可能是配置问题,但是我找不到。
@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
来实现。
正如 M. Deinum 所说,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;
}
下一步是用 JPA 替换普通 Hibernate,用 OpenEntityManagerInViewFilter 替换 OpenSessionInViewFilter。
谢谢 M. Deinum。
我将项目从 Spring 4.3.9.RELEASE + Hibernate 4.3.11.Final 升级到 Spring Boot 2.1.4.RELEASE 和 Hibernate 5.3.9.Final。查询仍然工作正常,但我得到 LazyInitializationException
一些 @OneToMany
class 成员。
首先,我从 @Transaction
服务检索对象,该对象引用了 @OneToMany
列表。该集合返回到控制器,然后从那里返回到 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 中,同样的事情不再起作用了。我已经尝试通过设置 spring.jpa.open-in-view=true
来启用 OEMIV,但即使这样似乎也不起作用或者它在某处被覆盖了。
如果我为该集合启用 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 的经验很少,所以我被卡住了。可能是配置问题,但是我找不到。
@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
来实现。
正如 M. Deinum 所说,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;
}
下一步是用 JPA 替换普通 Hibernate,用 OpenEntityManagerInViewFilter 替换 OpenSessionInViewFilter。
谢谢 M. Deinum。