-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 7 posts ] 
Author Message
 Post subject: LazyInitializationException with Session#get() NOT load()
PostPosted: Thu Feb 19, 2009 10:30 am 
Beginner
Beginner

Joined: Fri Nov 14, 2008 7:11 am
Posts: 31
Hi,

I have a problem with the differences of Session#load() and Session#get() in combination with a reflexive one-to-many dependency.

I have a entity Unit with the following (simplified) implementation:

Code:
@Entity
public class Unit{

   private Long id;
   private String name;
   private Unit parentUnit;
   private List<Unit> childUnits = new ArrayList<Unit>();

   public Unit() {
      super();
   }

   public Unit(final String name) {
      super();
      this.name = name;
   }

   @Id
   @GeneratedValue(strategy = GenerationType.AUTO)
   public Long getId() {
      return id;
   }

   public void setId(final Long id) {
      this.id = id;
   }

   @Basic(optional = false)
   @Column(nullable = false, length = 255)
   public String getName() {
      return name;
   }

   public void setName(final String name) {
      this.name = name;
   }

   @ManyToOne(optional = true)
   public Unit getParentUnit() {
      return parentUnit;
   }

   public void setParentUnit(final Unit parentUnit) {
      this.parentUnit = parentUnit;
   }

   @OneToMany(mappedBy = "parentUnit", fetch = FetchType.LAZY)
   @Cascade(value = { CascadeType.ALL })
   public List<Unit> getChildUnits() {
      return childUnits;
   }

   public void addChildUnit(final Unit childUnit) {
      childUnits.add(childUnit);
      childUnit.setParentUnit(this);
   }

   public void removeChildUnit(final Unit childUnit) {
      childUnits.remove(childUnit);
      childUnit.setParentUnit(null);
   }
   
   public void removeAllChildUnits()
   {
      childUnits.clear();
   }

   protected void setChildUnits(final List<Unit> childUnits) {
      this.childUnits = childUnits;
   }



This represents a tree where a unit may have multiple child units. If a unit is deleted all the child units should be move to the parent. Example:

Root (Unit)
|
|--- Child 1 (Unit)
|
|--- Grandchild 1 (Unit)
|
|--- Grandchild 2 (Unit)
|
|--- Child 2 (Unit)
|
|--- Child 3 (Unit)

If "Child 1" is deleted the tree should look like this afterwards:

Root (Unit)
|
|--- Child 2 (Unit)
|
|--- Child 3 (Unit)
|
|--- Grandchild 1 (Unit)
|
|--- Grandchild 2 (Unit)


"Grandchild 1" and "Grandchild 2" are moved to "Root" as their parent.

Here is the code using Session#load() that works fine. I am using multiple session to simulate a client-server-communication (as we are using GWT we cannot use the OpenSessionInViewFilter).

Code:
SessionFactory sessionFactory = (SessionFactory) applicationContext
            .getBean("sessionFactory", SessionFactory.class);
      
      // Load a unit in the first transaction
      Session session = sessionFactory.openSession();
      Transaction tx = session.beginTransaction();

      Unit unit = (Unit) session.load(Unit.class, 2L);

      tx.commit();
      session.close();

      session = sessionFactory.openSession();
      tx = session.beginTransaction();

      // Delete the unit in the second transaction
      session.lock(unit, LockMode.NONE); // Simple lock as I known that unit is unchanged here
      final Unit parent = unit.getParentUnit();
      
      // Add childs to my parent
      List<Unit> childUnits = unit.getChildUnits();
      for (Unit childUnit : childUnits) {
         parent.addChildUnit(childUnit);
      }
      // Remove me from my parent
      parent.removeChildUnit(unit);
      
      unit.removeAllChildUnits();
      
      session.update(parent);
      session.delete(unit);

      tx.commit();
      session.close();


If I change the line

Code:
Unit unit = (Unit) session.load(Unit.class, 2L);


to

Code:
Unit unit = (Unit) session.get(Unit.class, 2L);


I get the following exception:

Code:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: net.test.Unit.childUnits, no session or session was closed
   at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:380)
   at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:372)
   at org.hibernate.collection.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:365)
   at org.hibernate.collection.AbstractPersistentCollection.write(AbstractPersistentCollection.java:205)
   at org.hibernate.collection.PersistentBag.add(PersistentBag.java:297)
   at net.test.Unit.addChildUnit(Unit.java:81)
   at net.test.UnitTest.delete(UnitTest.java:47)
   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:585)
   at org.springframework.test.context.junit4.SpringTestMethod.invoke(SpringTestMethod.java:160)
   at org.springframework.test.context.junit4.SpringMethodRoadie.runTestMethod(SpringMethodRoadie.java:233)
   at org.springframework.test.context.junit4.SpringMethodRoadie$RunBeforesThenTestThenAfters.run(SpringMethodRoadie.java:333)
   at org.springframework.test.context.junit4.SpringMethodRoadie.runWithRepetitions(SpringMethodRoadie.java:217)
   at org.springframework.test.context.junit4.SpringMethodRoadie.runTest(SpringMethodRoadie.java:197)
   at org.springframework.test.context.junit4.SpringMethodRoadie.run(SpringMethodRoadie.java:143)
   at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.invokeTestMethod(SpringJUnit4ClassRunner.java:160)
   at org.junit.internal.runners.JUnit4ClassRunner.runMethods(JUnit4ClassRunner.java:51)
   at org.junit.internal.runners.JUnit4ClassRunner$1.run(JUnit4ClassRunner.java:44)
   at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:27)
   at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:37)
   at org.junit.internal.runners.JUnit4ClassRunner.run(JUnit4ClassRunner.java:42)
   at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:97)
   at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:45)
   at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
   at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
   at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
   at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
   at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)


I investigated this behaviour with the debugger and found out the following.
The load() returns a proxy unit on which the getParent() returns a persistent unit with a session attached to the lazy collection childUnits (you can see this in the debugger). Therefore the addChildUnit() call works fine.

The get() returns a persitent unit on which the getParent() returns a persistent unit as well but WITHOUT a session attached to the lazy collection childUnits. Therefore the addChildUnit() call causes the LazyInitializationException.

I am completely confused now as I haven't thought that this problem might be caused by the differences of load() and get().

Any help would be greatly appreciated.

Hibernate version: hibernate-core-3.3.1.GA

Mapping documents: see annotations

Name and version of the database you are using: MySQL 5.0

The generated SQL (show_sql=true):

Code:
Hibernate: select unit0_.id as id0_1_, unit0_.name as name0_1_, unit0_.parentUnit_id as parentUnit3_0_1_, unit1_.id as id0_0_, unit1_.name as name0_0_, unit1_.parentUnit_id as parentUnit3_0_0_ from UNIT unit0_ left outer join UNIT unit1_ on unit0_.parentUnit_id=unit1_.id where unit0_.id=?
Hibernate: select childunits0_.parentUnit_id as parentUnit3_1_, childunits0_.id as id1_, childunits0_.id as id0_0_, childunits0_.name as name0_0_, childunits0_.parentUnit_id as parentUnit3_0_0_ from UNIT childunits0_ where childunits0_.parentUnit_id=?


Top
 Profile  
 
 Post subject:
PostPosted: Thu Feb 19, 2009 11:20 am 
Expert
Expert

Joined: Thu Jan 08, 2009 6:16 am
Posts: 661
Location: Germany
That really seems to be a strange behaviour...

I looked at session.lock's api doc and I am not sure if the documentation is right, it says:
Quote:
This may (...) to simply reassociate a transient instance with a session

how can a transient instance be reassociated to a session? By definition:
Hibernate Reference Guide wrote:
an object is transient if it has just been instantiated using the new operator, and it is not associated with a Hibernate Session

So why would you want to get a lock on it. Maybe in the API "transient" should be "detached"?? What do the others think?

Back to your case: Try to merge your object before locking it.

_________________
-----------------
Need advanced help? http://www.viada.eu


Top
 Profile  
 
 Post subject:
PostPosted: Thu Feb 19, 2009 2:25 pm 
Expert
Expert

Joined: Wed Mar 03, 2004 6:35 am
Posts: 1240
Location: Lund, Sweden
Hmmm... when you think of it a bit more this is not so strange after all. Session.load() gives you a proxy for the 'unit'. Nothing more happens in the first transaction. In the second transaction you lock() the 'unit' to the new session and either this initializes the proxy or the call to unit.getParentUnit() does it. In any case, the 'parent' object is now a proxy that is attached to the second session, and everything works out as expected.

When you use session.get() the 'unit' will be initialized but it's 'parent' property is a proxy that is attached to the first session. This doesn't change when you call session.lock(unit) with the second session since this only reattached the 'unit', not the 'parent'.

So, a simple solution is to add 'session.lock(parent, LockMode.NONE)' as well. Or, you could add an annotation that cascades the lock to the parent unit. Eg.

Code:
@ManyToOne(optional = true)
@Cascade({org.hibernate.annotations.CascadeType.LOCK})
public Unit getParentUnit() {
   return parentUnit;
}


Top
 Profile  
 
 Post subject:
PostPosted: Fri Feb 20, 2009 4:43 am 
Beginner
Beginner

Joined: Fri Nov 14, 2008 7:11 am
Posts: 31
Thanks nordborg, that has helped me out.

Just one more question: what would be a best practise for my scenario? When should I use get() and when load()? And would it be better to lock the parent programatically or by cascading?

Thanks in advance,
Ole


Top
 Profile  
 
 Post subject:
PostPosted: Fri Feb 20, 2009 6:04 am 
Expert
Expert

Joined: Wed Mar 03, 2004 6:35 am
Posts: 1240
Location: Lund, Sweden
It's hard to give any general advice what is best. In our project we are always using session.get(), since this will detect missing items immediately and not at some time in the future. We are also using session.lock() and as far as I know we have no cascading. This means that we are vulnerable to the same problem with lazy initialization as you described in the first post. It pops up from time to time. The main problem is that in the second session we don't always know the history of the item was loaded in the first session and which relations that have been initialized and which are just proxies.

What we have found to work best is to reload a new instance with session.get() in the second session. Eg instead of:

Code:
session.lock(unit, LockMode.NONE);


we would do:

Code:
unit = (Unit)session.get(Unit.class, unit.getId());


It may result in a bit increased database activity but we don't worry to much about it since in most cases, the entity will be available in the second-level cache.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Feb 23, 2009 4:51 am 
Expert
Expert

Joined: Thu Jan 08, 2009 6:16 am
Posts: 661
Location: Germany
nordborg, you are totally right with your explanation with load, get and lock in two sessions. In my post I was just wondering why the API-doc states that lock reassociates a transient instance with a session. I mean, if its transient, it has never been associated to a session before (only if you removed it). I think, "transient" should be "detached", what do you think?

One other question, I am just curious: Why do you call get() and not merge()?
nordborg wrote:
we would do:

Code:
unit = (Unit)session.get(Unit.class, unit.getId());


_________________
-----------------
Need advanced help? http://www.viada.eu


Top
 Profile  
 
 Post subject:
PostPosted: Mon Feb 23, 2009 6:24 am 
Expert
Expert

Joined: Wed Mar 03, 2004 6:35 am
Posts: 1240
Location: Lund, Sweden
Quote:
I think, "transient" should be "detached", what do you think?


I agree.

Quote:
Why do you call get() and not merge()?


Because I don't want to update the object. I just want an object that I know I can use without lazy initialization problems. merge() should also work and is better if there are changes that should be saved.

Another reason... I am not 100% sure but I think that maybe merge() wasn't in the API when we started to use Hibernate. It was a long time ago with an early Hibernate 3 beta. Some solutions tend to stick around...


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 7 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.