简体   繁体   中英

JPA (Hibernate) - Session/Transaction and lazy loading

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(PersonDa oImplHibernate.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(BlockJUnit4Cla ssRunner.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.RemoteTestRunne r.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

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)

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM