views:

62

answers:

4

Hello all!

I have a problem with Hibernate and the LazyInitializationException. I searched and find a lot of answers, but I can not use them to solve my problem, because I have to say, I am new to Hibernate.

I run JUnit tests, in case of the error this one:

@Test
public void testAddPerson() {
    Set<Person> persons = service.getAllPersons();

    // create new person
    Person person = new Person();
    person.setEmail("[email protected]");
    Project testProject = serviceProj.findProjectById(1);

    HashSet<Project> lister = new HashSet<Project>();
    lister.add(testProject);
    person.setProjects(lister);
    service.addPerson(person);

    testProject.getPersons().add(person);
    ...
}

The last shown line:

testProject.getPersons().add(person);

throws this error:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.doe.john.domain.Project.persons, no session or session was closed

Person and Project are bidirectional n:m:

Person.java:

@ManyToMany(mappedBy="persons")
private Set<Project> projects = new HashSet<Project>();

Project.java:

@ManyToMany
@JoinTable(name = "Project_Person",
    joinColumns = {@JoinColumn(name="project_id", referencedColumnName="id")},
    inverseJoinColumns = {@JoinColumn(name="person_id", referencedColumnName="id")}
)
private Set<Person> persons = new HashSet<Person>();

So what's the problem?

+1  A: 

The problem is that by default the collection is lazily loaded. This means that it wont actually be loaded from the database until it is being accessed. In order to load it you will need a active session/transaction.

The easy way out is to change it to FethType.EAGER which makes sure that the collection is populated straight away.

--update--

I've recently had the very same problem and I ended up modifying my actual service to deal with this sort of thing. Declare a addPerson method in your ProjectService class.

public void addPersonTo(Project project, Person person)
{
  project = em.merge(project); //EntityManager, not sure what you are using but you get the idea hopefully
  project.addPerson(person);
}
willcodejavaforfood
Yes, but if I would do here FetchType.EAGER, I have in my real application things loaded which are not neccessary.
Tim
Do I need the line "testProject.getPersons().add(person);" ? I add a project to a person. So it should be in the n:m join tbale, shouldn't it?
Tim
That means you have to go for my first suggestion...
willcodejavaforfood
It has nothing to do with that, once serviceProj.findProjectById(1); has completed your transaction is over and you cannot change that collection without a new transaction
willcodejavaforfood
hm, deleting the line "testProject.getPersons().add(person);" and everything is okay, but I do not know if the joined table will have the "connection" between this person and the project.
Tim
It wont have that connection unless you actually add it to the collection. You are responsible for setting up the entities correctly. You need a transaction to do that.
willcodejavaforfood
But I thought it is bidirectionial with a join table. So I have person_project table with personId and projectId. And then I add the project to a person and update the person, so there must be an entry in the person_project table. Is that right?
Tim
@Tim - I don't think so, but could be wrong
willcodejavaforfood
Unfortunately, you are right! There is no entry in project_person database table. It seems that only the mapping owner (in this case the project) can set the entry of the database table?
Tim
You have to make sure your entity has all the right connections. Did you look at my updated answer where I suggest you change your service class to let you add stuff to collections?
willcodejavaforfood
Sorry, I did not see your edit. My DaoImpl looks normally like: public Project addProject(Project p) { SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); Session sess = sessionFactory.getCurrentSession(); Transaction tx = sess.beginTransaction(); sess.save(p); sess.flush(); tx.commit(); return p; } So I have to put your lines of code into DaoImpl? (I have IService, ServiceImpl, IDao, DaoImpl)
Tim
A: 

Can you try

person.getProjects().Add(testProject)

instead of

HashSet<Project> lister = new HashSet<Project>();
lister.add(testProject);
person.setProjects(lister);

You should be doing this since otherwise you would be blowing away a hibernate managed collection.

Rohith
I did this, but same error in line testProject.getPersons().add(person);
Tim
A: 

From hibernate reference:

By default, Hibernate3 uses lazy select fetching for collections and lazy proxy fetching for single-valued associations. These defaults make sense for most associations in the majority of applications.

If you set hibernate.default_batch_fetch_size, Hibernate will use the batch fetch optimization for lazy fetching. This optimization can also be enabled at a more granular level.

Please be aware that access to a lazy association outside of the context of an open Hibernate session will result in an exception. For example:

s = sessions.openSession();
Transaction tx = s.beginTransaction();

User u = (User) s.createQuery("from User u where u.name=:userName")
.setString("userName", userName).uniqueResult();
Map permissions = u.getPermissions();

tx.commit();
s.close();

Integer accessLevel = (Integer) permissions.get("accounts");  // Error!

Since the permissions collection was not initialized when the Session was closed, the collection will not be able to load its state. Hibernate does not support lazy initialization for detached objects. This can be fixed by moving the code that reads from the collection to just before the transaction is committed.

Alternatively, you can use a non-lazy collection or association, by specifying lazy="false" for the association mapping. However, it is intended that lazy initialization be used for almost all collections and associations. If you define too many non-lazy associations in your object model, Hibernate will fetch the entire database into memory in every transaction.

On the other hand, you can use join fetching, which is non-lazy by nature, instead of select fetching in a particular transaction. We will now explain how to customize the fetching strategy. In Hibernate3, the mechanisms for choosing a fetch strategy are identical for single-valued associations and collections.

So you have to close the session after accessing the collection!

Instead of :

service.addPerson(person);

testProject.getPersons().add(person);

I think you should have:

testProject.getPersons().add(person);
service.addPerson(person);
virgium03
I think the method service.addPerson closes the session so call it at the end.
virgium03
I changed the order to testProject.getPersons().add(person); service.addPerson(person); Unfortunately same error.
Tim
Please be aware that access to a lazy association outside of the context of an open Hibernate session will result in an exception. You are opening and closing sessions in the service methods (are you using spring btw) and when trying to add the person to the project you are outside the scope of the project session.
virgium03
okay, so do I need this line: testProject.getPersons().add(person);? I did this because I think I have to perform on the person and the project side if there is an update: I add a project to a person and save the person. Do I have to add the new person to the project for the n:m relation?
Tim
if you set the project on the person and update the person, you don't need to manually add it to the collection of persons on the project.
virgium03
Yes, and I think I do this?!
Tim
when you are accessing testProject.getPersons() you are outside of a session scope. if you can, try to manually open a session and close it afterward, just to see if it works.
virgium03
you can also check http://docs.jboss.org/hibernate/core/3.3/reference/en/html/example-parentchild.html for some additional details
virgium03
+1  A: 

Put your code inside a transaction - this will solve the problem for you

denis_k
how can I do this?
Tim
My DaoImpl looks like: public void updateProject(Project p) { SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); Session sess = sessionFactory.getCurrentSession(); Transaction tx = sess.beginTransaction(); sess.update(p); tx.commit(); } So I have a transaction?!
Tim
@Tim: No, you don't. After you exit your `updateProject`, your `Project p` is detached. And if you try to access any collection of `p` you get LazyInit. In order to avoid this, you should put your `getPersons()` call BETWEEN `beginTransaction()` and `commit()` calls
denis_k