2

I'm developing a Spring Boot based web application. I heavily rely on @ComponentScan and @EnableAutoConfiguration and no explicit XML configuration in place.

I have the following problem. I have a JPA-Annotated Entity class called UserSettings:

@Entity public class UserSettings {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @OneToMany(cascade = CascadeType.ALL)
    private Set<Preference> preferences; // 'Preference' is another @Entity class

    public UserSettings() {
         this.preferences = new HashSet<Preference>();
    }

// some more primitive properties, Getters, Setters...
}

I followed this tutorial and created a repository interface that extends JpaRepository<UserSettings,Long>.

Furthermore, I have a UserManager bean:

@Component public class SettingsManager {

@Autowired
UserSettingsRepository settingsRepository;

@PostConstruct
protected void init() {
    // 'findGlobalSettings' is a simple custom HQL query 
    UserSettings globalSettings = this.settingsRepository.findGlobalSettings();
    if (globalSettings == null) {
        globalSettings = new UserSettings();
        this.settingsRepository.saveAndFlush(globalSettings);
    }
}

Later in the code, I load the UserSettings object created here, again with the findGlobalSetttings query.

The problem is: Every time I try to access the @OneToMany attribute of the settings object, I get the following exception:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role org.example.UserSettings.preferences, could not initialize proxy - no Session

I understand that each HTTP Session has its own Hibernate Session, as described in the accepted answer of this question, but that does not apply in my case (currently I'm testing this within the same HTTP Session), which is why I have no idea where this exception comes from.

What am I doing wrong and how can I achieve circumvent the error?

Community
  • 1
  • 1
Alan47
  • 4,750
  • 4
  • 26
  • 48
  • A Hibernate Session is not a Http Session. As soon as yuor transaction is over the hibernate session is closed. Not is a hibernate session attached to a http session. It is attached to the current transaction (or when using it with open session/entitymnager in view the request). – M. Deinum Jun 24 '14 at 11:28
  • Thanks for pointing this out. However, I believe that the error resides somewhere in the implementation of the JpaRepository class that is auto-generated by Spring at runtime. I just tried to retrieve the desired instance directly by using the EntityManager - which works perfectly fine. Doing the same retrieval with the JpaRepository yields an object of the same class with the same ID (!) but that object is different with respect to the == operator to the object returned by the entity manager... – Alan47 Jun 24 '14 at 12:04
  • No it isn't it is the same problem and has nothing to do with Spring Data. Retrieving the object from 2 different entitymanagers will always get you another object. The problem is simple as you store a reference in the http session, the original hibernate session is gone when you try to do things on that entity from the session. Either retrieve it each time you need it or fully initialize your object before putting it in the session. – M. Deinum Jun 24 '14 at 12:17
  • I just double checked it: the underlying Hibernate 'Session' object has the very same Java Object ID when storing my UserSettings object as it does when retrieving the UserSettings object. As far as I can tell (I'm using Vaadin as a web UI toolkit and Vaadin4Spring), I'm still in the same HTTP session and request, too. Regardless, I get a different UserSettings object when storing and querying for it and I get the exception stated above. – Alan47 Jun 24 '14 at 12:43
  • As stated twice before the session is closed as soon as the transaction ends unless you are using open session in view which will maintain the hibernate session until the view is rendered. Else the hibernate session is gone as soon as the tx is done. – M. Deinum Jun 24 '14 at 13:32

3 Answers3

4

If you want to be able to access mapped entities outside the transaction (which you seem to be doing), you need to flag it as an "eager" join. i.e.

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
Steve
  • 8,541
  • 5
  • 39
  • 60
  • 1
    If you haven't used it before, it's probably worth noting that by flagging a join as eager, JPA/Hibernate will add the join to all queries and generate the joined objects. As you might imagine, over-use can lead to big slow queries which generate huge graphs of entities. So just be careful not to use it everywhere. :) – Steve Jun 24 '14 at 15:52
  • Yes, I thought so. I will be careful about it. Currently, the domain model in my application is very simple and the element reachability closures are rather small too, so I guess that I can live with it for now. If the performance hit becomes noticeable, I will have to think of something else anyways. Thanks for pointing this out. – Alan47 Jun 24 '14 at 21:34
  • btw ... If it does become a problem, the usual alternative is to create a `@Transactional` method, which returns a full model of what will be rendered on the page, rather than passing your entities to whatever does your view rendering. Either that or you can just iterate through lazy-loaded collections inside a `@Transactional` method, to force them to be generated. – Steve Jun 25 '14 at 08:04
  • I'll make sure to remember that, could come in handy. Our data model is rather simple (at least the part which is O/R mapped) but the application itself is rather complex. So perhaps we will need to make use of this soon. Thanks! – Alan47 Jun 25 '14 at 13:45
3

This question has been answered beautifully by @Steve. However, if you still want to maintain your lazy loading implementation, you may want to try this

import javax.servlet.Filter;

import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.hibernate4.support.OpenSessionInViewFilter;  

@Configuration
@ComponentScan
public class AppConfig {

    @Bean
    public FilterRegistrationBean filterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(openSessionInView());
        registration.addUrlPatterns("/*");

        return registration;
    }

    @Bean
    public Filter openSessionInView() {
        return new OpenSessionInViewFilter();
    }
}

What this configuration does is, it registers a Filter on requests to path "/*" which keeps your Hibernate Session open in your view.

This is an anti-pattern and must be used with care.

NOTE: As of Spring Boot 1.3.5.RELEASE, when you use the default configuration with Spring Data JPA auto-configuration, you shouldn't encounter this problem

Community
  • 1
  • 1
Julius Krah
  • 413
  • 2
  • 11
  • OpenEntityManagerInViewInterceptor is auto to be registered, but it's not work when the logic is happened in the filter, for me, I register the OpenEntityManagerInViewFilter and it solved my problem. – Rocky Hu Mar 09 '17 at 03:07
1

I faced similar issue in spring boot application, after googling I'm able to fix this issue by adding the following code to my application.

    @Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder, DataSource dataSource) {
    LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
    entityManagerFactoryBean.setDataSource(dataSource);
    entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
    Properties jpaProperties = new Properties();
    jpaProperties.put("hibernate.enable_lazy_load_no_trans", true);
    entityManagerFactoryBean.setJpaProperties(jpaProperties);
    return entityManagerFactoryBean;
}

Referred here.

Community
  • 1
  • 1