Hello everyone, i have a strange phenomenon regarding refreshing and updating lazy collections.
When running the testRefresh() method below everything is fine. But if you comment out the three code lines, the last line inside the try block will cause an NonUniqueObjectException. This is because the collection of Bs in A1 has been hydrated in line "Collection bs = a.getBs();". The root object A1 stays the same, also B1 stays the same, but the back reference in B (still inside the collection if the root object) to A1 changes.
When calling "s.refresh(a)" the back reference to A1 inside the B1 object does not longer point to the original A1 object. (In deed, they also are not equal in memory either.)
Is this a common behaviour in cases when using several sessions and detached, hierarchical objects?
Could you please confirm if it's a bug in the Hibernate implementation, or just the wrong usage of the session (thou i can't find where as this example is *really* simple). But if the use was wrong, please tell what to do to correct this.
Hibernate version: 3.1
Mapping documents:
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="hibernatedemo.models">
<class name="A" table="A" select-before-update="true" lazy="false">
<id name="id" type="long" unsaved-value="null">
<column name="A_ID"/>
<generator class="increment"/>
</id>
<property name="name" column="A_NAME"/>
<set name="bs" lazy="true" inverse="true" cascade="all-delete-orphan">
<key column="B_ID"/>
<one-to-many class="B"/>
</set>
</class>
<class name="B" table="B" select-before-update="true" lazy="false">
<id name="id" type="long" unsaved-value="null">
<column name="B_ID"/>
<generator class="increment"/>
</id>
<property name="name" column="B_NAME"/>
<many-to-one name="a" column="A_ID" class="A" cascade="none" lazy="false"/>
</class>
</hibernate-mapping>
Structure Table A :Code:
A_ID : int <pk>
A_NAME : VARCHAR2(50)
Table Data for A :Code:
A_ID : 1
A_NAME : A1
Structure Table B:Code:
B_ID : int <pk>
B_NAME : VARCHAR2(50)
A_ID : int <pk>
Table Data for B:Code:
B_ID : 1
B_NAME : B1
A_ID : 1
Class A:Code:
package hibernatedemo.models;
import java.util.*;
public class A {
protected Long id;
protected String name;
protected Set<B> bs;
public A() {
this.bs = new HashSet<B>();
}
public Long getId() {
return id;
}
public void setId( Long id ) {
this.id = id;
}
public String getName() {
return name;
}
public void setName( String name ) {
this.name = name;
}
public Set<B> getBs() {
return bs;
}
public void setBs( Set<B> bs ) {
this.bs = bs;
}
public boolean equals( Object o ) {
if ( !( o instanceof A ) ) {
return false;
}
final A a = (A) o;
if ( id != null ? !id.equals( a.id ) : a.id != null ) {
return false;
}
if ( name != null ? !name.equals( a.name ) : a.name != null ) {
return false;
}
return true;
}
public int hashCode() {
int result;
result = ( id != null ? id.hashCode() : 0 );
result = 29 * result + ( name != null ? name.hashCode() : 0 );
return result;
}
}
Class B:Code:
package hibernatedemo.models;
public class B {
protected Long id;
protected String name;
protected A a;
public B() {
}
public Long getId() {
return id;
}
public void setId( Long id ) {
this.id = id;
}
public String getName() {
return name;
}
public void setName( String name ) {
this.name = name;
}
public A getA() {
return a;
}
public void setA( A a ) {
this.a = a;
}
public boolean equals( Object o ) {
if ( this == o ) {
return true;
}
if ( !( o instanceof B ) ) {
return false;
}
final B b = (B) o;
if ( id != null ? !id.equals( b.id ) : b.id != null ) {
return false;
}
if ( name != null ? !name.equals( b.name ) : b.name != null ) {
return false;
}
return true;
}
public int hashCode() {
int result;
result = ( id != null ? id.hashCode() : 0 );
result = 29 * result + ( name != null ? name.hashCode() : 0 );
return result;
}
}
Testcase that procuses an exception WHEN COMMENTS EXCLUDED:Code:
package hibernatedemo;
import junit.framework.TestCase;
import hibernatedemo.models.*;
import org.hibernate.*;
import org.hibernate.cfg.Configuration;
import java.util.*;
public class RefreshTest
extends TestCase {
public void testRefresh() {
try {
SessionFactory sf = new Configuration().configure().buildSessionFactory();
Session s = sf.openSession();
assertFalse( s.isDirty() );
//retrieve all As with name 'A1' (should be only 1)
Collection col = s.createQuery( "from A a where a.name like 'A1'" ).list();
assertNotNull( col );
assertEquals( 1, col.size() );
A a = (A) col.iterator().next();
//hydrate lazy object
assertEquals( "A1", a.getName() );
// assertTrue( a.equals( a.getBs().iterator().next().getA() ) );
// Collection bs = a.getBs();
//close old session and reopen a new one
s.close();
s = null;
assertNull( s );
s = sf.openSession();
assertNotNull( s );
assertFalse( s.isDirty() );
// assertTrue( a.equals( a.getBs().iterator().next().getA() ) );
s.refresh( a );
s.saveOrUpdate( a );
} catch ( HibernateException he ) {
he.printStackTrace();
fail();
} catch ( Exception e ) {
e.printStackTrace();
fail();
}
}
}
Full stack trace of any exception that occurs:Code:
org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [hibernatedemo.models.A#1]
at org.hibernate.engine.StatefulPersistenceContext.checkUniqueness(StatefulPersistenceContext.java:628)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:258)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:216)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:70)
at org.hibernate.impl.SessionImpl.fireSaveOrUpdate(SessionImpl.java:520)
at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:513)
at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:509)
at hibernatedemo.RefreshTest.testRefresh(RefreshTest.java:219)
Name and version of the database you are using:
Oracle 8.1.7[quote][/quote]