10

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);
    }
Jon Taylor
  • 7,740
  • 5
  • 26
  • 53
  • The problem seems to be in your controller. Would you mind posting the code? – a better oliver Dec 15 '13 at 22:02
  • I (or more precisley Evgney) seems to have solved my problem by getting me to access the first element in the list before returning it from the transaction. Does this force it to load all objects in the list rather than returning some form of proxy? – Jon Taylor Dec 15 '13 at 22:06
  • I am adding the controller though if it helps – Jon Taylor Dec 15 '13 at 22:08
  • 2
    ´team.getMembers()´ just returns the proxy, i.e. no actual data. That's the whole point of lazy loading ;) So only when you access an item of the list Hibernate loads something. So in your Controller the list is still not initialized. – a better oliver Dec 15 '13 at 22:14

4 Answers4

13

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.

Community
  • 1
  • 1
Evgeni Dimitrov
  • 19,437
  • 29
  • 105
  • 137
  • But am I not accessing the list of members and returning the members from within the transaction? How can I get around this? Can I force it to load the members themselves? – Jon Taylor Dec 15 '13 at 20:51
  • Also I don't want to fetch eagerly. I want to fetch lazily for performance. But I cant seem to access the contents of these objects even within the transactions so that's why I am baffled. – Jon Taylor Dec 15 '13 at 20:55
  • Are you sure your transaction manager is properly configured? Can you show me the spring configuration. – Evgeni Dimitrov Dec 15 '13 at 20:58
  • Take a look at this tutorial to see how to configure the transaction manager http://www.byteslounge.com/tutorials/spring-with-hibernate-persistence-and-transactions-example Shortly you need to pass the session factory to the transaction manager so that it can start the transacttion before the method and close it after that(with AOP). – Evgeni Dimitrov Dec 15 '13 at 21:02
  • I have added the spring configuration (or part of it that I believe is relevant) to the end of my question – Jon Taylor Dec 15 '13 at 21:17
  • Try to remove the datasource from the transaction manager. Otherwise everything looks OK to me. – Evgeni Dimitrov Dec 15 '13 at 21:34
  • Removing it makes no difference. Is the problem being caused by the jackson mapper itself? – Jon Taylor Dec 15 '13 at 21:39
  • I have added another part of the configuration showing the mapper for converting output to JSON – Jon Taylor Dec 15 '13 at 21:41
  • Since the error occurred even in transactional methods, as you said, I assume that there is nothing with the Jackson Mapper. In getPeopleForTeam() if you do 'team.getMembers().get(0).getName()', before the return, the exception is thrown again, right? – Evgeni Dimitrov Dec 15 '13 at 21:47
  • That worked :/ but why? I am a little confused. Is this a common workaround? – Jon Taylor Dec 15 '13 at 21:57
  • 1
    try returning a new collection -> return new ArrayList(team.getMembers()); – marco.eig Dec 15 '13 at 22:14
  • Thanks all, I realise now that the list I was getting was a proxy. In fact I now just call .size() on it to make it resolve to the proper list. – Jon Taylor Dec 16 '13 at 16:50
9

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

Abdelghani Roussi
  • 2,275
  • 2
  • 15
  • 32
0

I faced the same issue @JsonIgnore worked for me, what I understand is you can fetch the set of objects lazy and use it programatically but for the JSON HTTP response it is ignored.

alowsarwar
  • 733
  • 7
  • 22
0

I found one more solution to this Problem.

You can explicit load the objects in the method so that it is already created whenever required by JSON parser. I tried it and this works for me.

Example in my case:

public Company getCompany(Long companyId){

    Company company = companyDao.findById(companyId);
    // For avoiding Lazy initialization error. If not done, JSON serializer is unhappy
    // as Address object is not created till that moment when JSON serialization is done.
    for(Address address: company.getAddressSet() ){
        addressDao.findById(address.getId());
    }
    return company;
}