views:

124

answers:

2

Hello!

I have a Java EE project and the MySQL database is managed with an ORM. I worked a lot with Hibernate to learn what I am doing wrong and I think I understand session/transaction, but I do not know how I can solve this in my case/architecture.

I have a Project and a Person which are in a bi-directional n:m relation with a join table. The Project is the mapping-owner. Now I want to delete a Person which is connected to a Project.

So I thought, I can do this:

Person person = findPersonById(personId);
Set<Project> projects = person.getProjects();
Iterator<Project> iterator = projects.iterator();
while (iterator.hasNext()) {
    Project project = iterator.next();
    if (project.getPersons().contains(person)) {
        project.getPersons().remove(person);
        projectDao.updateProject(project);
    }
}
personDao.removePerson(personId);

But I get an error in this line:

Iterator<Project> iterator = projects.iterator();

But it seems it has to do with:

Person person = findPersonById(personId);

and:

public Person findPersonById(int personId) {
    SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
    Session sess = sessionFactory.getCurrentSession();

    Transaction tx = sess.beginTransaction();
    try {
        Person person = (Person)sess.createQuery("from Person where id = "+personId).list().get(0);
        tx.commit();
        return person;
    }
    catch (IndexOutOfBoundsException ex) {
        return null;
    }
}

Here I make a transaction and only get the person object, but no corresponding projects. And the iterator will try to get them, but can not, because my transaction/session is closed. This is because of my architecture:

I have: IPersonService -> IPersonImplementation -> IPersonDao -> IPersonDaoImplementation and only the method in IPersonDaoImplementation opens and closes the transaction. So in my ServicePersonImplementation I can not do several DB related stuff (I do not want to have FetchType.EAGER in my @ManyToMany annotation, because this would load to much not needed stuff). Because once it returns from my DaoImplementation I can not perform other actions on the returned person (in my case get all projects which are combined with this person).

So what can I do in my case?

Best Regards Tim.

Update: PersonDaoImpl, remove method:

public void removePerson(int personId){
    Person p = findPersonById(personId);

    SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
    Session sess = sessionFactory.getCurrentSession();

    Transaction tx = sess.beginTransaction();
    sess.delete(p);
    sess.flush();
    tx.commit();
}

Trace of the error:

ERROR: org.hibernate.LazyInitializationException - failed to lazily initialize a collection of role: com.testdomain.testproject.domain.Person.projects, no session or session was closed org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.testdomain.testproject.domain.Person.projects, no session or session was closed at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:383) at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:375) at org.hibernate.collection.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:368) at org.hibernate.collection.AbstractPersistentCollection.read(AbstractPersistentCollection.java:111) at org.hibernate.collection.PersistentSet.iterator(PersistentSet.java:186) at com.testdomain.testproject.dao.impl.PersonDaoImplHibernate.removePerson(PersonDaoImplHibernate.java:71) at com.testdomain.testproject.service.impl.PersonServiceImpl.removePerson(PersonServiceImpl.java:88) at com.testdomain.testproject.service.PersonTest.testRemovePerson(PersonTest.java:180) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20) at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28) at org.junit.runners.ParentRunner.run(ParentRunner.java:236) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:46) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197) Failed deleting Person: failed to lazily initialize a collection of role: com.testdomain.testproject.domain.Person.projects, no session or session was closed

+2  A: 

What you really want to do is push all of the Person removal code down into the PersonDao's remove() method. Inside that method, you want to do the following:

  • begin transaction
  • Find Person instance by the id
  • Get its Project set and remove it from all of them
  • Remove the Person instance itself
  • commit transaction

In JPA, you shouldn't need to do anything more than that, however I believe in straight Hibernate you may need to saveOrUpdate then changes made to each Project as well as remove the Person.

So your code might look something like this:

public void removePerson(int personId) {
  SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
  Session sess = sessionFactory.getCurrentSession();
  Transaction tx = sess.beginTransaction();

  try {
    Person person = (Person)sess.createQuery("from Person where id = "+personId).list().get(0);
    Set<Project> projects = person.getProjects();
    for (Project project : projects) {
      if (project.getPersons().contains(person)) {
        project.getPersons().remove(person);
        sess.saveOrUpdate(project);
      }
    }

    sess.delete(person);
    tx.commit();
  }  catch (Exception e) {
     System.err.println("Failed deleting Person: "+e.getMessage());
  }
}

(Note: this is roughly closer to pseudo code and has not been tested inside an actual IDE)

Ophidian
I added _person.getProjects();_ before _tx.commit();_ but unfortunately same result.
Tim
My bad, I should have read the question much more closely. I've updated my answer.
Ophidian
I used your code, but I get the same error in this line: for (Project project : projects) { - lazy initialization exception
Tim
I'm an idiot, the findPersonById has its own session so it can't be re-used in my removePerson method. Try it with the query directly in the removePerson method.
Ophidian
Thanks a lot, now it works! My JUnit tests are green :-) So I should do all such performances into the transaction of the Dao implementation? Because I have another problem like this, so I will do this in this kind.
Tim
Yes, all the cleanup for removing an object should really be carried out within the Dao.
Ophidian
+1  A: 

You have two options here really. The first one is to perform this in your service/DAO implementation.

projectDao.removePersonFrom(Person personToRemove, Project projectToRemoveFrom);

personDao.removeProjectFrom(Project projectToRemove, Person personToRemoveFrom);

I usually have a Model class associated with a view and sometimes I make certain methods in them transactional (I use Spring) to do this kind of stuff.

--edit--

Inside the daos you create your session/transaction and then you can do the code that fails. The code you have posted should work fine.

willcodejavaforfood
And what should be the vontent of the methode removePersonFrom? Because there is one person and only one project. But I do not know the projects which the person is associated to. I also have Spring implemented for Dependency Injection.
Tim
I meant I use it to declare transaction boundries
willcodejavaforfood
You are also right, I have to put all the stuff from the ServiceImpl to the DaoImpl inside the transaction.
Tim