首页 > 解决方案 > Hibernate 4 -> 5 升级导致 LazyInitializationException

问题描述

我将一个项目从 Spring 4.3.9.RELEASE + Hibernate 4.3.11.Final 升级到 Spring Boot 2.1.4.RELEASE 和 Hibernate 5.3.9.Final。查询仍然可以正常工作,但我得到LazyInitializationException了一些@OneToMany班级成员。

@OneToMany首先,我从服务中检索具有对 List的引用的对象@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 加载模式,一切正常。

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 方面的经验相当少,所以我被卡住了。这可能是一个配置问题,但我找不到它。

标签: springhibernatespring-bootlazy-initialization

解决方案


正如 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。


推荐阅读