Search code examples
springhibernatespring-bootlazy-initialization

Hibernate 4 -> 5 upgrade leads to LazyInitializationException


I upgraded a project from Spring 4.3.9.RELEASE + Hibernate 4.3.11.Final to Spring Boot 2.1.4.RELEASE and Hibernate 5.3.9.Final. The queries are still working fine, but I'm getting LazyInitializationException with some @OneToMany class members.

First I retrieve the object, which has a reference to a @OneToMany List, from the @Transaction service. The collection is returned to the controller, and from there it goes back to Spring to be serialized into a json. The controller has @RestController, so it knows what to do.

In Spring 4.3.9.RELEASE + Hibernate 4.3.11.Final everything was fine, even though OpenEntityManagerInView wasn't enabled by configuration and the collection wasn't loaded with EAGER mode. But in Spring Boot 2.1.4.RELEASE and Hibernate 5.3.9.Final the same thing doesn't work anymore. I've tried enabling OEMIV, by setting spring.jpa.open-in-view=true, but even this doesn't seem to work or it's being overriden somewhere.

If I enable EAGER loading mode for that collection, everything works fine.

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;

The problem is caused by field linkToCategories. If I configure @OneToMany(fetch = FetchType.EAGER) everything works fine.

Application configuration:

    @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);
    }

Later edit: After a lot of debugging, the difference between the old and the new Hibernate functionality is in the HibernateTransactionManager. In the method doGetTransaction(), in Hibernate 4 it finds the SessionHolder object when calling

TransactionSynchronizationManager.getResource(getSessionFactory())

while in Hibernate 5 it doesn't.

    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);
        }
    }

In the method doBegin, a new session is created and set on the txObject for every request.

        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);
        }

My experience with Hibernate is fairly small, so here I'm stuck. It's probably a configuration thing, but I can't find it.


Solution

  • As M. Deinum was saying, the Spring 4.3.9.RELEASE + Hibernate 4.3.11.Final configuration was loading OpenSessionInViewFilter, which explains why all the queries were going through successfully. After configuring the same filter in Spring Boot, everything is back to normal. Add the following bean to register the filter:

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

    The next step is to replace plain Hibernate with JPA, and OpenSessionInViewFilter with OpenEntityManagerInViewFilter.

    Thanks M. Deinum.