Yadu Krishnan
Yadu Krishnan

Reputation: 3522

Initialize all lazy loaded collections in hibernate

I have tried most of the solutions for this issue I am facing, but I am still not clear.

I am using Hibernate 4.

I have parent-child relationships in my entity. By default, I have used lazy loading for all the collections in my parent entity. However, in some cases, I need to load the whole object graph. In such cases, I want to force hibernate to load all the collections. I know that by using criteria.setFetchMode("collectionName",FetchMode.JOIN), I can load a specific collection. However, if I tried to do that for multiple collections, I am getting org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags.

Here is my code:

Entity

Ignoring a few fields and getter and setter methods

public class Employee {
    @Id
    @GeneratedValue
    @Column(name = "EmployeeId")
    private long id;

    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @Fetch(FetchMode.SELECT)
    @JoinColumn(name = "EmployeeId")
    @LazyCollection(LazyCollectionOption.TRUE)
    private List<EmployeeAddress> employeeAddresses;

    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @Fetch(FetchMode.SELECT)
    @JoinColumn(name = "EmployeeId")
    @LazyCollection(LazyCollectionOption.TRUE)
    private List<EmployeeLanguage> employeeLanguages;

}

HibernateUtil

public HibernateUtil{

    public TEntity findById(Class<TEntity> clazz, long id) {
            Session session = sessionFactory.getCurrentSession();
            Criteria criteria = session.createCriteria(clazz);
            criteria.add(Restrictions.eq("id",id));
            ClassMetadata metadata = sessionFactory.getClassMetadata(clazz);

            String[] propertyNamesArray = metadata.getPropertyNames();
            for(int i=0;i<propertyNamesArray.length;i++){
                criteria.setFetchMode(propertyNamesArray[i], FetchMode.JOIN);
            }
            return (TEntity)criteria.uniqueResult();
        }
}

Service

private void getAllEmployee(Employee employee) {
         //invoke the findAll method of hibernateutil
}

I want to have eager-loading of all the collections of Employee in findAll() method. Please tell me how to get that done.

Adding the stacktrace when using FetchType.SELECT inside the HIbernateUtil

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.myapp.hr.entity.Employee.employeeAddresses, could not initialize proxy - no Session
    org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:566)
    org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:186)
    org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:545)
    org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:124)
    org.hibernate.collection.internal.PersistentBag.iterator(PersistentBag.java:266)
    java.util.Collections$UnmodifiableCollection$1.<init>(Collections.java:1099)
    java.util.Collections$UnmodifiableCollection.iterator(Collections.java:1098)
    com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serializeContents(CollectionSerializer.java:90)
    com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serializeContents(CollectionSerializer.java:23)
    com.fasterxml.jackson.databind.ser.std.AsArraySerializerBase.serialize(AsArraySerializerBase.java:186)
    com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:569)
    com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:597)
    com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:142)
    com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:118)
    com.fasterxml.jackson.databind.ObjectMapper.writeValue(ObjectMapper.java:1819)
    org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.writeInternal(MappingJackson2HttpMessageConverter.java:253)
    org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:207)
    org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:148)
    org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor.handleReturnValue(HttpEntityMethodProcessor.java:125)
    org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:71)
    org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:122)
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:749)
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:690)
    org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:83)
    org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:945)
    org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:876)
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:961)
    org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:852)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:618)
    org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    org.apache.catalina.filters.CorsFilter.handleNonCORS(CorsFilter.java:439)
    org.apache.catalina.filters.CorsFilter.doFilter(CorsFilter.java:178)

Upvotes: 3

Views: 10823

Answers (2)

Erik Gillespie
Erik Gillespie

Reputation: 3959

I mentioned this in a comment in my other answer but it's different enough and I don't want to lose Gimby's insights by editing my original answer.

You can use reflection to lookup the fields with the @LazyCollection annotation and call Hibernate.initialize() on them. It would look something like this:

    public static <T> void forceLoadLazyCollections(Class<T> tClass, T entity) {
        if (entity == null) {
            return;
        }
        for (Field field : tClass.getDeclaredFields()) {
            LazyCollection annotation = field.getAnnotation(LazyCollection.class);
            if (annotation != null) {
                try {
                    field.setAccessible(true);
                    Hibernate.initialize(field.get(entity));
                } catch (IllegalAccessException e) {
                    log.warning("Unable to force initialize field: " + field.getName());
                }
            }
        }
    }

Upvotes: 3

Erik Gillespie
Erik Gillespie

Reputation: 3959

Change your fetch mode to FetchMode.SELECT. Using a join to pull in a collection can only be applied to one collection/table because it causes lots of duplicate data to be pulled across the wire. Using separate selects to pull in collections can be used as much as you want.

Upvotes: 1

Related Questions