6

Note: See my own answer to this question for an example of how I solved this issue.

I am getting the following exception in my Spring MVC 4 + Hibernate 4 project:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.mysite.Company.acknowledgements, could not initialize proxy - no Session

After reading a lot of other questions about this issue, I understand why this exception occurs, but I am not sure how to fix it in a good way. I am doing the following:

  1. My Spring MVC controller calls a method in a service
  2. The service method invokes a method in a DAO class
  3. The method in the DAO class fetches an entity object through Hibernate and returns it to the calling service
  4. The service returns the fetched object to the controller
  5. The controller passes the object to the view (JSP)
  6. The view tries to iterate on a many-to-many association that is lazily loaded (and is thus a proxy object)
  7. The exception is thrown because the session is closed at this point, and the proxy cannot load the associated data

I have previously worked with PHP and doctrine2, and this way of doing things caused no problems. I am trying to figure out the best way to solve this, because the solutions that I found so far don't seem so great:

  • Load the association eagerly, potentially loading lots of unnecessary data
  • Call Hibernate.initialize(myObject.getAssociation()); - this means that I have to loop through associations to initialize them (I guess), and just the fact that I have to do this, kind of makes the lazy loading less neat
  • Using a Spring filter to leave the session open in the views, but I doubt that this is a good thing to do?

I tried to use @Transactional in my service, but without luck. This makes sense because I am trying to access data that has not yet been loaded after my service method returns. Ideally I would like to be able to access any association from within my view. I guess the drawback of initializing the associations within my service is that I have to explicitly define which data I need - but this depends on the context (controller) in which the service is used. I am not sure if I can do this within my controller instead without losing the abstraction that the DBAL layer provides. I hope that makes sense. Either way, it would be great if I didn't have to always explicitly define which data I want to be available to my view, but just let the view do its thing. If that is not possible, then I am just looking for the most elegant solution to the problem.

Below is my code.

View

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<h1><c:out value="${company.name}" /> (ID: <c:out value="${company.id}" />)</h1>

<c:forEach var="acknowledgement" items="${company.acknowledgements}">
    <p><c:out value="${acknowledgement.name}" /></p>
</c:forEach>

Controller

@Controller
public class ProfileController {
    @Autowired
    private CompanyService companyService;

    @RequestMapping("/profile/view/{id}")
    public String view(Model model, @PathVariable int id) {
        Company company = this.companyService.get(id);
        model.addAttribute("company", company);

        return "viewCompanyProfile";
    }
}

Service

@Service
public class CompanyServiceImpl implements CompanyService {
    @Autowired
    private CompanyDao companyDao;

    @Override
    public Company get(int id) {
        return this.companyDao.get(id);
    }
}

DAO

@Repository
@Transactional
public class CompanyDaoImpl implements CompanyDao {
    @Autowired
    private SessionFactory sessionFactory;

    @Override
    public Company get(int id) {
        return (Company) this.sessionFactory.getCurrentSession().get(Company.class, id);
    }
}

Company entity

@Entity
@Table(name = "company")
public class Company {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    // Other fields here

    @ManyToMany
    @JoinTable(name = "company_acknowledgement", joinColumns = @JoinColumn(name = "company_id"), inverseJoinColumns = @JoinColumn(name = "acknowledgement_id"))
    private Set<Acknowledgement> acknowledgements;

    public Set<Acknowledgement> getAcknowledgements() {
        return acknowledgements;
    }

    public void setAcknowledgements(Set<Acknowledgement> acknowledgements) {
        this.acknowledgements = acknowledgements;
    }

    // Other getters and setters here
}

Acknowledgement entity

@Entity
public class Acknowledgement {
    @Id
    private int id;

    // Other fields + getters and setters here

}

mvc-dispatcher-servlet.xml (a part of it)

<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="packagesToScan">
            <list>
                <value>com.mysite.company.entity</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQL9Dialect</prop>
            </props>
        </property>
    </bean>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

    <tx:annotation-driven />

Thanks in advance!

ba0708
  • 8,528
  • 11
  • 61
  • 97

5 Answers5

10

As @Predrag Maric said writing service methods with different initialization for entity assotiations might be an option.

OpenSessionInView is a quit discussable pattern, which can be a solution in your case, though.

Another option is to set

<prop key="hibernate.enable_lazy_load_no_trans">true</prop>

property. It is designed to solve org.hibernate.LazyInitializationException problem and is available since hibernate 4.1.6.

So what does this property do? It simply signals Hibernate that it should open a new session if session which is set inside current un-initialised proxy is closed. You should be aware that if you have any other open session which is also used to manage current transaction, this newly opened session will be different and it may not participate into the current transaction unless it is JTA. Because of this TX side effect you should be careful against possible side effects in your system.

Find more information here: http://blog.harezmi.com.tr/hibernates-new-feature-for-overcoming-frustrating-lazyinitializationexceptions and Solve Hibernate Lazy-Init issue with hibernate.enable_lazy_load_no_trans

Community
  • 1
  • 1
overthrown
  • 121
  • 5
2

The simplest and most transparent solution is the OSIV pattern. As I'm sure you aware, there is plenty of discussion about this (anti)pattern and the alternatives both on this site and elsewhere so no need to go over that again. For example:

Why is Hibernate Open Session in View considered a bad practice?

However, in my opinion, not all criticisms of OSIV are entirely accurate (e.g. the transaction will not commit till your view is rendered? Really? If you are using the Spring implementation then https://stackoverflow.com/a/10664815/1356423)

Also, be aware that JPA 2.1 introduced the concept of Fetch Graphs which give you more control over what is loaded. I haven't tried it yet but maybe this if finally a solution for this long standing problem!

http://www.thoughts-on-java.org/2014/03/jpa-21-entity-graph-part-1-named-entity.html

Community
  • 1
  • 1
Alan Hay
  • 20,941
  • 2
  • 47
  • 97
  • Thank you for your answer. I tried out the entity graph approach, and it is a nice and clean solution for this. It solved the problem, and I will write an answer with my solution to anyone who is interested. It does, however, seem as if the entity graph implementation [is not so flexible (yet)](http://stackoverflow.com/questions/27360966/using-jpa-entity-graph-with-complex-conditions). But it solved the problem! – ba0708 Dec 08 '14 at 19:05
1

My vote goes to Hibernate.initialize(myObject.getAssociation()) in service layer (which also means @Transactional should be moved from DAO to service methods)

IMHO, service methods should return all data that is required by the caller. If the caller wants to display some association on the view, it is service's responsibility to supply that data. This means you could have several methods that apparently do the same thing (return Company instance), but depending on the caller, different associations could be fetched.

On one project we had kind of configuration class, which contained information on what associations should be fetched, and we had a single service method which also accepted that class as a parameter. This approach meant we have only one method which is flexible enough to support all callers.

@Override
public Company get(int id, FetchConfig fc) {
    Company result = this.companyDao.get(id);
    if (fc.isFetchAssociation1()) {
        Hibernate.initialize(result.getAssociation1());
    }
    ...
    return result;
}
Predrag Maric
  • 21,996
  • 4
  • 45
  • 64
  • I thought there had to be a way to prevent this, but your solution makes sense - and I agree that service methods should return the data required by the caller. But what if I have "nested associations", e.g. if `company.someAssociation` also has an association that I would like to fetch? If I have to call `Hibernate.initialize()` on all of the associations that I would like to fetch, won't I end up doing a whole lot of looping through data objects in my services, just to ensure that all of the data that I want is fetched? That just seems like something the ORM should do for me, in my opinion. – ba0708 Dec 04 '14 at 17:58
  • Yes, you are right about that, with nested assaciations it could end up being bulky and ugly code. But, in my experience you will rarely need associations from more than second level of depth in one service. So that code could be moderately ugly, not too much. Alternative approach is, as you alredy stated, to bind the session for the entire requst, whuch doesn't sound right. The truth is probably somewhere in the middle. – Predrag Maric Dec 04 '14 at 18:43
  • Why would you favour this solution over OSIV? You are writing more code to have the same N+1 selects issue (which is the primary argument against OSIV). – Alan Hay Dec 05 '14 at 09:20
  • @AlanHay My main problem with OSIV is mixing of layers, exposing hibernate session to view/controller layer is just wrong. Plus, there are cases (depending on framework used for view layer) where you need to modify your entities in some ways to ensure that the page is rendered ok (for example, you need to instantiate some relation which is null, because some component on page won't render if it is null). OSIV will lead to persisting that instantiated relation, because entities are managed. – Predrag Maric Dec 05 '14 at 09:45
  • 'OSIV will lead to persisting that instantiated relation, because entities are managed.' That is a misconception (if you are using the Spring implementation anyway) - see my earlier answer. 'My main problem with OSIV is mixing of layers' - aren't you mixing layers in reverse? – Alan Hay Dec 05 '14 at 09:55
  • @AlanHay Another thing, my proposal with `FetchConfig` could be implemented in such a way so it [fetches](http://docs.jboss.org/hibernate/stable/entitymanager/reference/en/html/querycriteria.html#querycriteria-from-fetch) the associations in a single query., instead of `Hibernate.initialize()`. Anyway, `hibernate.enable_lazy_load_no_trans` from @overthrown's answer, and fetch graph from yours also sound like good approaches. – Predrag Maric Dec 05 '14 at 09:56
  • @AlanHay I looked at the link in your post, I stand corrected on persisting instantiated relation point. No, I don't think I'm mixing layers in reverse, I'm creating a service method whose outcome is configurable to suit various needs. – Predrag Maric Dec 05 '14 at 10:01
1

For others who are looking for a solution, here is how I solved the problem.

As Alan Hay pointed out, JPA 2.1+ supports entity graphs, which ended up solving my problem. To make use of it, I change my project to use the javax.persistence.EntityManager class instead of the SessionFactory, which would then hand me the current session. Here is how I configured it in my dispatcher servlet configuration (some things excluded):

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.1.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">

    <jee:jndi-lookup id="dataSource" jndi-name="java:/comp/env/jdbc/postgres" expected-type="javax.sql.DataSource"/>

    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="packagesToScan" value="dk.better.company.entity, dk.better.user.entity" />
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
        </property>
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQL9Dialect</prop>
                <prop key="hibernate.show_sql">true</prop>
            </props>
        </property>
    </bean>

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>

    <tx:annotation-driven />
</beans>

Below is an example of a DAO class.

@Transactional
public class CompanyDaoImpl implements CompanyDao {
    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public Company get(int id) {
        EntityGraph<Company> entityGraph = this.entityManager.createEntityGraph(Company.class);
        entityGraph.addAttributeNodes("acknowledgements");

        Map<String, Object> hints = new HashMap<String, Object>();
        hints.put("javax.persistence.loadgraph", entityGraph);

        return this.entityManager.find(Company.class, id, hints);
    }
}

I found that if I did not use @PersistenceContext but Autowired instead, then the database session was not closed when rendering the view, and data updates were not reflected in subsequent queries.

For more information on entity graphs, I encourage you to read the following articles:

http://www.thoughts-on-java.org/2014/03/jpa-21-entity-graph-part-1-named-entity.html

http://www.thoughts-on-java.org/2014/04/jpa-21-entity-graph-part-2-define.html

ba0708
  • 8,528
  • 11
  • 61
  • 97
0

Trigger in the service layer requires lazy loading of the Set's size method.

Tools:

public class HibernateUtil {
    /**
     * Lazy = true when the trigger size method is equal to lazy = false (load all attached)
     */
    public static void triggerSize(Collection collection) {
        if (collection != null) {
            collection.size();
        }
    }
}

in your service method:

Apple apple = appleDao.findById('xxx');
HibernateUtil.triggerSize(apple.getColorSet());
return apple;

then use apple in controller, everything is ok!

nvnpnco
  • 11
  • 1