Hello,
I am working with the detached entities pattern, and I am facing a concurrent merge issue.
I reproduced it with the small following example.
A class 'A' has two children b1 and b2 :
Code:
public class A
{
private Long id;
private Integer version;
private B b1;
private B b2;
public B getB1() { return b1; }
public void setB1(B b1) { this.b1 = b1; }
public B getB2() { return b2;}
public void setB2(B b2) { this.b2 = b2; }
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Integer getVersion() { return version; }
public void setVersion(Integer version) { this.version = version; }
}
Code:
public class B {
private Long id;
private Integer version;
private String description;
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Integer getVersion() { return version; }
public void setVersion(Integer version) { this.version = version; }
public boolean equals(Object obj) { ... }
public int hashCode() { ... }
}
The mapping files are very simple :
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="fr.test.domain">
<class name="A" table="A">
<id name="id" column="A_ID">
<generator class="increment" />
</id>
<version name="version" column="VERSION">
</version>
<many-to-one name="b1" cascade="all" class="fr.test.domain.B" />
<many-to-one name="b2" cascade="all" class="fr.test.domain.B" />
</class>
</hibernate-mapping>
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="fr.test.domain">
<class name="B" table="B">
<id name="id" column="B_ID">
<generator class="increment" />
</id>
<version name="version" column="VERSION">
</version>
<property name="description" column="DESCRIPTION"/>
</class>
</hibernate-mapping>
When the user sets a detached entity to b1, if this object points to the same entity than b2 _ which is a possible business case _
I cannot save the A instance anymore :
- if I use
session.saveOrUpdate(a), a NonUniqueObjectException is thrown
- if I use
session.merge(a), no exception is thrown but b1 modification is lost !
Code:
public class Test extends TestCase
{
private A a;
protected void setUp() throws Exception
{
//init
a = init();
//load B1
B newB1 = loadB(a.getB2().getId());
newB1.setDescription("b1");
a.setB1(newB1);
}
public void testSaveOrUpdate()
{
assertNotNull(a);
// throws [b]NonUniqueObjectException[/b]
//
saveA(a);
}
public void testMerge()
{
assertNotNull(a);
// Works but...
a = mergeA(a);
// .... [b]b1 modification is lost[/b]
assertEquals("b1", a.getB1().getDescription());
}
//-------------------------------------------------------------------------
// Internal methods
//-------------------------------------------------------------------------
protected A init() {
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction tx = session.beginTransaction();
A a = new A();
B b = new B();
b.setDescription("b");
a.setB1(b);
a.setB2(b);
session.save(a);
tx.commit();
session.close();
return a;
}
protected void saveA(A a)
{
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction tx = session.beginTransaction();
session.saveOrUpdate(a);
tx.commit();
session.close();
}
protected A mergeA(A a)
{
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction tx = session.beginTransaction();
A result = (A) session.merge(a);
tx.commit();
session.close();
return result;
}
protected B loadB(Long id)
{
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction tx = session.beginTransaction();
B b = (B)session.get(B.class, id);
tx.commit();
session.close();
return b;
}
}
I would have expected Hibernate to merge correctly, or at least to throw an exception notifying that it cannot merge the b1 object since it has been modified...
Does anyone knows how to solve this problem ?
Best Regards
Bruno