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=?