I am utterly confused, I have been creating my first Spring application with hibernate and I can not seem to spot my error when lazy loading objects from my database.
My Models are as follows
The Team class
@Entity
public class Team {
@Id
@Column
@GeneratedValue(strategy=GenerationType.AUTO)
private int id;
@Column
private String name;
@Column
private String description;
@OneToMany(fetch=FetchType.LAZY , cascade = CascadeType.ALL, mappedBy="team")
@JsonIgnore
public List<Person> members;
//Constructors and setters getters ommited
}
The Person class
@Entity
@Table(name="`person`")
public class Person {
@Id
@Column
@GeneratedValue(strategy=GenerationType.AUTO)
private int id;
@Column
private String name;
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="team")
@JsonIgnore
private Team team;
//omitted constructor and methods
}
I then have My Data Access Objects which all follow the same pattern as this
@Repository
public class TeamDaoImpl implements TeamDao {
@Autowired
private SessionFactory session;
@Override
public void add(Team team) {
session.getCurrentSession().save(team);
}
@Override
public void edit(Team team) {
session.getCurrentSession().update(team);
}
@Override
public void delete(int teamId) {
session.getCurrentSession().delete(getTeam(teamId));
}
@Override
public Team getTeam(int teamId) {
return (Team) session.getCurrentSession().get(Team.class, teamId);
}
@Override
public List getAllTeam() {
return session.getCurrentSession().createQuery("from Team").list();
}
}
In all these methods I make sure to use the current session to perform these actions. From my understanding this makes sure that it places these calls into the existing transaction which is create in the service class as follows:
@Service
public class PersonServiceImpl implements PersonService {
Logger log = Logger.getLogger(PersonServiceImpl.class);
@Autowired
PersonDao personDao;
@Autowired
TeamDao teamDao;
//Ommitted other methods
@Transactional
public List getPeopleForTeam(int teamId) {
log.info("Getting people for team with id " + teamId);
Team team = teamDao.getTeam(teamId);
return team.getMembers();
}
}
My understanding was that the @Transactional
annotation should put that method into a single transaction.
This all compiles fine but when I run it I get the following error
org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: failed to lazily initialize a collection of role: model.Team.members, no session or session was closed; nested exception is
com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: model.Team.members, no session or session was closed
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.writeInternal(MappingJackson2HttpMessageConverter.java:207)
org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:179)
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:148)
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:90)
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:189)
org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:69)
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:122)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:745)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:686)
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:936)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:827)
javax.servlet.http.HttpServlet.service(HttpServlet.java:621)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:812)
javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
What am I doing wrong? Is my understanding of the @Transactional annotation incorrect? If so how should I go about lazy loading collections inside the object yet fetching them within certain transactional methods?
EDIT:
This is the relevant part of my spring configuration
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close" p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.databaseuri}" p:username="${jdbc.username}" />
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation">
<value>classpath:hibernate.cfg.xml</value>
</property>
<property name="configurationClass">
<value>org.hibernate.cfg.AnnotationConfiguration</value>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${jdbc.dialect}</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="dataSource" ref="dataSource" />
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
This is another part of the configuration
<mvc:annotation-driven>
<mvc:message-converters>
<!-- Use the HibernateAware mapper instead of the default -->
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="com.bt.opsscreens.mappers.HibernateAwareObjectMapper" />
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
This is the part of the controller
@RequestMapping(value="/{teamId}/people", method=RequestMethod.GET)
public @ResponseBody List<Person> getPeopleForTeam(@PathVariable int teamId) {
return personService.getPeopleForTeam(teamId);
}
You probably call the service method from a controller(for example). What happens:
-controller method
-call service method
-start transaction
-hibernate do lazy loading
-end transaction
-end service method
you try to access somethind from the lazy initialied list, but no hibernate transaction here
-end controller method
lazy loading means that hibernate have a proxy object that stores only the id of the entity and executes select statement only when it's needed(e.g. access of some property of the entity).
You can take a look at Open session in view
but it's considered bad practice Why is Hibernate Open Session in View considered a bad practice?.
Try to fetch the collections eagerly.
Edit: 1)Hibernate requires everything to be executed in transaction e.g.
Transaction tx = null;
try{
tx = session.beginTransaction();
...
tx.commit();
}catch (HibernateException e) {
if (tx!=null) tx.rollback();
e.printStackTrace();
}finally {
session.close();
}
2)Lazy loading: Hibernate creates proxy object (it don't fire statement to the DB). When you try to access the proxy, a statement is sent to the DB and the result is retreived(this need to be in hibernate transaction.)
3)Spring simplifies the transaction management by intercepting the given method with AOP. First it starts a transaction, then calls the method and commit or rollbacks it.
getPeopleForTeam() return the proxy. Then somewhere outside of a transaction you access some property and hibernate tries to fire a select statement.
You are accessing a lazy-loaded collection outside the Hibernate Session , so you should either change from lazy loading to eager loading or add the annotation @JsonIgnore
before each @OneToMany
annotation in your model
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With