Search code examples
javaspringhibernatespring-mvc

Loading collection of String into model from persistent object with hibernate and spring


When I try to place a list of stings taken from an object, I've loading in from a database via hibernate, I'm getting this exception.

org.hibernate.LazyInitializationException: could not initialize proxy - no Session

The method I've used to load the list is within a transaction. But when I try to place the list in the model I get the above exception. I'm taking from this that hibernate is requiring me to have even this line of code within a transaction also. But given that it's not a database operation why is this so?

@RequestMapping(value="{id}", method=RequestMethod.POST)
public String addComment(@PathVariable String id, Model model, String comment) {
    personService.addComment(Long.parseLong(id), comment);
    Person person = personService.getPersonById(Long.parseLong(id));
    model.addAttribute(person);
    List<String> comments = personService.getComments(id);
    model.addAttribute(comments);
    return "/Review";
}

Service object.

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;


public class PersonServiceImpl implements PersonService {

private Workaround personDAO;


public PersonServiceImpl() {
}

@Transactional(propagation=Propagation.REQUIRED, readOnly=false)
public void savePerson(Person person) { 
    personDAO.savePerson(person);
}

@Transactional(propagation=Propagation.REQUIRED, readOnly=false)
public Person getPersonById(long id) {
    return personDAO.getPersonById(id);
}

@Autowired
public void setPersonDAO(Workaround personDAO) {
    this.personDAO = personDAO;
}

@Transactional(propagation=Propagation.REQUIRED, readOnly=false)
public List<Person> getAllPeople() {
    return personDAO.getAllPeople();
}

@Transactional(propagation=Propagation.REQUIRED, readOnly=false)
public void addComment(Long id, String comment) {
    Person person = getPersonById(id);
    person.addComment(comment);
    savePerson(person);
}

@Transactional(propagation=Propagation.REQUIRED, readOnly=false)
public List<String> getComments(String id) {
    return personDAO.getComments(Long.parseLong(id));
}

}

DAO

import java.util.List;

import javax.persistence.ElementCollection;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;



@Repository
public class PersonDAO implements Workaround {
private SessionFactory sessionFactory;

@Autowired
public PersonDAO(SessionFactory sessionFactory) {
    this.sessionFactory = sessionFactory;   
}

private Session currentSession() {
    return sessionFactory.getCurrentSession();
}

public void addPerson(Person person) {
    currentSession().save(person);
}

public Person getPersonById(long id) {
    return (Person) currentSession().get(Person.class, id);
}


public void savePerson(Person person) {
    currentSession().save(person);
}

public List<Person> getAllPeople() {
    List<Person> people = currentSession().createQuery("from Person").list();
    return people;
    
}

public List<String> getComments(long id) {
    return getPersonById(id).getComments();
}

}


Solution

  • I am relatively new to Hibernate but I'll take a guess at this from my understanding of it.

    By default @OneToMany collections are lazily loaded. So when you load your Person object a proxy will be created in place of your actual comments list. This list won't be loaded until you call the getter for that list (ie getComments()) as you are, and even then I don't think the full list is loaded at once, more so one by one (yep multiple db calls) as you iterate through the list or the whole list at one if you call .size().

    However, I think the catch here is that you must load the list within the same session that you load the parent (Person) object. When you are loading the Person object you have a reference to the current session and then once you call the getComments() on that Person object the session is closed.

    I think to keep the whole process in one session you could manually open and close your session like so in your DAO class.

    public List<String> getComments(long id) {
        Session session = sessionFactory.openSession();
    
        List<String> comments = getPersonById(id).getComments();
    
        sessionFactory.getCurrentSession().flush();
        sessionFactory.getCurrentSession.close();
    
        return comments;
    }
    

    Setting the FetchType to EAGER would solve the problem, but from what I have read it is generally not recommended unless you always need the comments loaded with the Person object.