Reputation: 10599
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:
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:
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 neatI 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!
Upvotes: 7
Views: 29818
Reputation: 11
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!
Upvotes: 0
Reputation: 10599
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
Upvotes: 2
Reputation: 131
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
Upvotes: 11
Reputation: 23226
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
Upvotes: 2
Reputation: 24433
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;
}
Upvotes: 1