Hi,
I need to use deep clone object...but some Hibernate operation having an strange behavior: the saveOrUpdate method.
I load an object from the database, and I clone it. I modify the clone and save it into the database.
the behavior is different when I change the value on a child objet (work great) and I delete the child.
Deleting the child isn't impact into the data base!!!
if I use the merge operation change and delete are impact to the database.
if I use the saveOrUpdate operation only the change is impact!
For more clarity I make a JUnit Test Case:
Code:
package test.clone;
import java.util.ArrayList;
import java.util.Iterator;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import junit.framework.TestCase;
public class ParentTest extends TestCase {
private Parent parent;
private Child child_1;
private Child child_2;
private ClassPathXmlApplicationContext ctx;
private DAO dao;
protected void setUp() throws Exception {
super.setUp();
parent = new Parent("parent_field_1", "parent_field_2");
child_1 = new Child("child_field_1", "child_field_2");
child_2 = new Child("child_field_1", "child_field_2");
ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
dao = (DAO) ctx.getBean("DAO");
}
protected void tearDown() throws Exception {
super.tearDown();
}
public void testClone() {
System.out.println("\n \n TEST CASE testClone :");
parent.addChildren(child_1);
parent.addChildren(child_2);
Parent pclone = (Parent) parent.clone();
// change only Clone children's
for (Iterator iter = pclone.getChildren().iterator(); iter.hasNext();) {
Child element = (Child) iter.next();
element.setChild_field_1("CLONE_child_field_1");
element.setChild_field_2("CLONE_child_field_2");
}
System.out.println("ORIGINAL: "+parent);
System.out.println("CLONE : "+pclone);
// ORIGINAL
for (Iterator iter = parent.getChildren().iterator(); iter.hasNext();) {
Child element = (Child) iter.next();
assertEquals("child_field_1",element.getChild_field_1());
assertEquals("child_field_2",element.getChild_field_2());
}
// CLONE MODIFICATION
for (Iterator iter = pclone.getChildren().iterator(); iter.hasNext();) {
Child element = (Child) iter.next();
assertEquals("CLONE_child_field_1",element.getChild_field_1());
assertEquals("CLONE_child_field_2",element.getChild_field_2());
}
}
public void testUpdateCloneChildren_AND_SAVE_INTO_DATABASE() {
System.out.println("\n \n TEST CASE testUpdateCloneChildren_AND_SAVE_INTO_DATABASE :");
// save the original object into database
parent.addChildren(child_1);
parent = dao.merge(parent);
// find the original objet from the database
Parent parentDataBase = dao.findById(parent.getId());
// clone the original objet from the database
Parent pclone = (Parent) parentDataBase.clone();
// only change Clone children's
for (Iterator iter = pclone.getChildren().iterator(); iter.hasNext();) {
Child element = (Child) iter.next();
element.setChild_field_1("CLONE_child_field_1");
element.setChild_field_2("CLONE_child_field_2");
}
// update the clone object into database
Parent parentCloneDataBase = dao.merge(pclone);
assertEquals(parent.getId(), parentCloneDataBase.getId());
// check the correct update from the database
parentCloneDataBase = dao.findById(parent.getId());
assertEquals(parent.getId(), parentCloneDataBase.getId());
for (Iterator iter = parentCloneDataBase.getChildren().iterator(); iter.hasNext();) {
Child element = (Child) iter.next();
assertEquals("CLONE_child_field_1",element.getChild_field_1());
assertEquals("CLONE_child_field_2",element.getChild_field_2());
}
System.out.println("ORIGINAL NOT UPDATED : "+parent);
System.out.println("CLONE UPDATED DATABASE : "+parentCloneDataBase);
}
public void testDeleteCloneChildren_AND_MERGE_INTO_DATABASE() {
System.out.println("\n \n testDeleteCloneChildren_AND_MERGE_INTO_DATABASE :");
parent.addChildren(child_1);
// save the original object into database
parent = dao.merge(parent);
// find the original objet from the database
Parent parentDataBase = dao.findById(parent.getId());
// clone the original objet from the database
Parent pclone = (Parent) parentDataBase.clone();
// only remove Clone children's
ArrayList al = (ArrayList) pclone.getChildren();
for (int i = al.size()-1; i >= 0 ; i--) {
Child onechildclone = (Child) al.get(i);
pclone.removeChildren(onechildclone);
}
// update the clone object into database
Parent parentCloneDataBase = dao.merge(pclone);
assertEquals(parent.getId(), parentCloneDataBase.getId());
// check the correct delete from the database
parentCloneDataBase = dao.findById(parent.getId());
System.out.println("ORIGINAL NOT UPDATED : "+parent);
System.out.println("CLONE DELETED CHILDREN : "+pclone);
System.out.println("CLONE DELETED CHILDREN RETURN DROM DATABASE : "+parentCloneDataBase);
assertEquals(1, parent.getChildren().size());
assertEquals(0, pclone.getChildren().size());
assertEquals(0, parentCloneDataBase.getChildren().size());
}
public void testDeleteCloneChildren_AND_SAVE_INTO_DATABASE() {
System.out.println("\n \n testDeleteCloneChildren_AND_SAVE_INTO_DATABASE :");
parent.addChildren(child_1);
// save the original object into database
parent = dao.save(parent);
// find the original objet from the database
Parent parentDataBase = dao.findById(parent.getId());
// clone the original objet from the database
Parent pclone = (Parent) parentDataBase.clone();
// only remove Clone children's
ArrayList al = (ArrayList) pclone.getChildren();
for (int i = al.size()-1; i >= 0 ; i--) {
Child onechildclone = (Child) al.get(i);
pclone.removeChildren(onechildclone);
}
// update the clone object into database
Parent parentCloneDataBase = dao.save(pclone);
assertEquals(parent.getId(), parentCloneDataBase.getId());
// check the correct delete from the database
parentCloneDataBase = dao.findById(parent.getId());
System.out.println("ORIGINAL NOT UPDATED : "+parent);
System.out.println("CLONE DELETED CHILDREN : "+pclone);
System.out.println("CLONE DELETED CHILDREN RETURN DROM DATABASE : "+parentCloneDataBase);
assertEquals(1, parent.getChildren().size());
assertEquals(0, pclone.getChildren().size());
assertEquals(0, parentCloneDataBase.getChildren().size());
}
}
Why the testDeleteCloneChildren_AND_SAVE_INTO_DATABASE case doesn't delete the child element:
Parent [ | id: 1 | Parent_field_1: parent_field_1 | Parent_field_2: parent_field_2]
Quote:
--> Child [ | id: 1 | idParent: 1 | Child_field_1: child_field_1 | Child_field_2: child_field_2]
Result Console trace: testDeleteCloneChildren_AND_SAVE_INTO_DATABASE : WRONG RESULT Hibernate: insert into PARENT (parent_field_1, parent_field_2) values (?, ?)
Hibernate: insert into CHILD (child_field_1, child_field_2, ID_PARENT) values (?, ?, ?)
Hibernate: select parent0_.ID_PARENT as ID1_0_, parent0_.parent_field_1 as parent2_0_0_, parent0_.parent_field_2 as parent3_0_0_ from PARENT parent0_ where parent0_.ID_PARENT=?
Hibernate: select children0_.ID_PARENT as ID4_1_, children0_.ID_CHILD as ID1_1_, children0_.ID_CHILD as ID1_0_, children0_.child_field_1 as child2_1_0_, children0_.child_field_2 as child3_1_0_, children0_.ID_PARENT as ID4_1_0_ from CHILD children0_ where children0_.ID_PARENT=?
Hibernate: select parent0_.ID_PARENT as ID1_0_, parent0_.parent_field_1 as parent2_0_0_, parent0_.parent_field_2 as parent3_0_0_ from PARENT parent0_ where parent0_.ID_PARENT=?
Hibernate: select children0_.ID_PARENT as ID4_1_, children0_.ID_CHILD as ID1_1_, children0_.ID_CHILD as ID1_0_, children0_.child_field_1 as child2_1_0_, children0_.child_field_2 as child3_1_0_, children0_.ID_PARENT as ID4_1_0_ from CHILD children0_ where children0_.ID_PARENT=?
Hibernate: update PARENT set parent_field_1=?, parent_field_2=? where ID_PARENT=?
Hibernate: select parent0_.ID_PARENT as ID1_0_, parent0_.parent_field_1 as parent2_0_0_, parent0_.parent_field_2 as parent3_0_0_ from PARENT parent0_ where parent0_.ID_PARENT=?
Hibernate: select children0_.ID_PARENT as ID4_1_, children0_.ID_CHILD as ID1_1_, children0_.ID_CHILD as ID1_0_, children0_.child_field_1 as child2_1_0_, children0_.child_field_2 as child3_1_0_, children0_.ID_PARENT as ID4_1_0_ from CHILD children0_ where children0_.ID_PARENT=?
Hibernate: select parent0_.ID_PARENT as ID1_0_, parent0_.parent_field_1 as parent2_0_0_, parent0_.parent_field_2 as parent3_0_0_ from PARENT parent0_ where parent0_.ID_PARENT=?
Hibernate: select children0_.ID_PARENT as ID4_1_, children0_.ID_CHILD as ID1_1_, children0_.ID_CHILD as ID1_0_, children0_.child_field_1 as child2_1_0_, children0_.child_field_2 as child3_1_0_, children0_.ID_PARENT as ID4_1_0_ from CHILD children0_ where children0_.ID_PARENT=?
ORIGINAL NOT UPDATED : Parent [ | id: 1 | Parent_field_1: parent_field_1 | Parent_field_2: parent_field_2]
--> Child [ | id: 1 | idParent: 1 | Child_field_1: child_field_1 | Child_field_2: child_field_2]
CLONE DELETED CHILDREN : Parent [ | id: 1 | Parent_field_1: parent_field_1 | Parent_field_2: parent_field_2]
CLONE DELETED CHILDREN RETURN DROM DATABASE : Parent [ | id: 1 | Parent_field_1: parent_field_1 | Parent_field_2: parent_field_2]
--> Child [ | id: 1 | idParent: 1 | Child_field_1: child_field_1 | Child_field_2: child_field_2] testDeleteCloneChildren_AND_MERGE_INTO_DATABASE : OKHibernate: insert into CHILD (child_field_1, child_field_2, ID_PARENT) values (?, ?, ?)
Hibernate: insert into PARENT (parent_field_1, parent_field_2) values (?, ?)
Hibernate: update CHILD set child_field_1=?, child_field_2=?, ID_PARENT=? where ID_CHILD=?
Hibernate: select parent0_.ID_PARENT as ID1_0_, parent0_.parent_field_1 as parent2_2_0_, parent0_.parent_field_2 as parent3_2_0_ from PARENT parent0_ where parent0_.ID_PARENT=?
Hibernate: select children0_.ID_PARENT as ID4_1_, children0_.ID_CHILD as ID1_1_, children0_.ID_CHILD as ID1_0_, children0_.child_field_1 as child2_3_0_, children0_.child_field_2 as child3_3_0_, children0_.ID_PARENT as ID4_3_0_ from CHILD children0_ where children0_.ID_PARENT=?
Hibernate: select parent0_.ID_PARENT as ID1_0_, parent0_.parent_field_1 as parent2_2_0_, parent0_.parent_field_2 as parent3_2_0_ from PARENT parent0_ where parent0_.ID_PARENT=?
Hibernate: select children0_.ID_PARENT as ID4_1_, children0_.ID_CHILD as ID1_1_, children0_.ID_CHILD as ID1_0_, children0_.child_field_1 as child2_3_0_, children0_.child_field_2 as child3_3_0_, children0_.ID_PARENT as ID4_3_0_ from CHILD children0_ where children0_.ID_PARENT=?
Hibernate: delete from CHILD where ID_CHILD=?
Hibernate: select parent0_.ID_PARENT as ID1_0_, parent0_.parent_field_1 as parent2_2_0_, parent0_.parent_field_2 as parent3_2_0_ from PARENT parent0_ where parent0_.ID_PARENT=?
Hibernate: select children0_.ID_PARENT as ID4_1_, children0_.ID_CHILD as ID1_1_, children0_.ID_CHILD as ID1_0_, children0_.child_field_1 as child2_3_0_, children0_.child_field_2 as child3_3_0_, children0_.ID_PARENT as ID4_3_0_ from CHILD children0_ where children0_.ID_PARENT=?
ORIGINAL NOT UPDATED : Parent [ | id: 1 | Parent_field_1: parent_field_1 | Parent_field_2: parent_field_2]
--> Child [ | id: 1 | idParent: 1 | Child_field_1: child_field_1 | Child_field_2: child_field_2]
CLONE DELETED CHILDREN : Parent [ | id: 1 | Parent_field_1: parent_field_1 | Parent_field_2: parent_field_2]
CLONE DELETED CHILDREN RETURN DROM DATABASE : Parent [ | id: 1 | Parent_field_1: parent_field_1 | Parent_field_2: parent_field_2]Quote:
Parent.java
Code:
package test.clone;
import java.io.ObjectInputStream.GetField;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Parent {
private Integer id;
private String parent_field_1="";
private String parent_field_2="";
private List children = new ArrayList();
public Parent(){
}
public Parent(Parent object){
id = object.getId();
parent_field_1 = object.getParent_field_1();
parent_field_2 = object.getParent_field_2();
for (Iterator iter = object.getChildren().iterator(); iter.hasNext();) {
Child original_child = (Child) iter.next();
Child cloneChild = (Child) original_child.clone();
this.getChildren().add(cloneChild);
}
}
public Parent(String str_parent_field_1, String str_parent_field_2) {
parent_field_1 = str_parent_field_1;
parent_field_2 = str_parent_field_2;
}
public void addChildren (Child oneChild){
if (oneChild == null)
throw new IllegalArgumentException("Can't add a null child.");
if (oneChild.getParent() != null && oneChild.getParent().equals(this))
throw new IllegalArgumentException("child is already in this parent");
else if (oneChild.getParent() != null){
// on pourrait ajouter un test sur les conditions de contrats
oneChild.getParent().removeChildren(oneChild);
}
oneChild.setParent(this);
this.getChildren().add(oneChild);
}
public void removeChildren (Child oneChild){
if (oneChild == null)
throw new IllegalArgumentException("Can't add a null child.");
oneChild.setParent(null);
this.getChildren().remove(oneChild);
}
public List getChildren() {
return children;
}
public void setChildren(List children) {
this.children = children;
}
public String getParent_field_1() {
return parent_field_1;
}
public void setParent_field_1(String chield_fiel_1) {
this.parent_field_1 = chield_fiel_1;
}
public String getParent_field_2() {
return parent_field_2;
}
public void setParent_field_2(String chiled_field_2) {
this.parent_field_2 = chiled_field_2;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Object clone() {
return new Parent(this);
}
public String toString() {
StringBuffer strb = new StringBuffer();
strb.append("Parent [");
strb.append(" | id: "+ getId());
strb.append(" | Parent_field_1: "+getParent_field_1());
strb.append(" | Parent_field_2: "+getParent_field_2());
strb.append("]");
for (Iterator iter = getChildren().iterator(); iter.hasNext();) {
Object element = (Object) iter.next();
strb.append("\n");
strb.append(" --> "+element);
}
return strb.toString();
}
}
Quote:
Child.java
Code:
package test.clone;
import java.util.Iterator;
public class Child {
private Integer id;
private String child_field_1="";
private String child_field_2="";
private Parent parent;
public Child(){
}
public Child(Child object){
id = object.getId();
child_field_1 = object.getChild_field_1();
child_field_2 = object.getChild_field_2();
parent = object.getParent();
}
public Child(String str_child_field_1, String str_child_field_2) {
child_field_1 = str_child_field_1;
child_field_2 = str_child_field_2;
}
public Parent getParent() {
return parent;
}
public void setParent(Parent parent) {
this.parent = parent;
}
public String getChild_field_1() {
return child_field_1;
}
public void setChild_field_1(String child_field_1) {
this.child_field_1 = child_field_1;
}
public String getChild_field_2() {
return child_field_2;
}
public void setChild_field_2(String child_field_2) {
this.child_field_2 = child_field_2;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Object clone() {
return new Child(this);
}
public String toString() {
StringBuffer strb = new StringBuffer();
strb.append("Child [");
strb.append(" | id: "+ getId());
strb.append(" | idParent: "+getParent().getId());
strb.append(" | Child_field_1: "+getChild_field_1());
strb.append(" | Child_field_2: "+getChild_field_2());
strb.append("]");
return strb.toString();
}
}
Quote:
DAO.java
Code:
package test.clone;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
public class DAO extends HibernateDaoSupport {
public Parent save(Parent transientInstance) {
try {
getHibernateTemplate().saveOrUpdate(transientInstance);
return findById(transientInstance.getId());
} catch (RuntimeException re) {
throw re;
}
}
public Parent merge(Parent transientInstance) {
try {
return (Parent) getHibernateTemplate().merge(transientInstance);
} catch (RuntimeException re) {
throw re;
}
}
public Parent findById(Integer id) {
try {
Parent instance = (Parent) getHibernateTemplate().get(Parent.class, id);
return instance;
} catch (RuntimeException re) {
throw re;
}
}
public void delete(Parent persistentInstance) {
try {
getHibernateTemplate().delete(persistentInstance);
} catch (RuntimeException re) {
throw re;
}
}
}
Quote:
Mapping Files
Code:
<?xml version="1.0" encoding='UTF-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
<hibernate-mapping package="test.clone">
<class name="Parent" table="PARENT">
<id name="id" column="ID_PARENT" type="java.lang.Integer">
<generator class="native">
<!-- param name="sequence">S_PFA_MODIFICATION_REF</param-->
</generator>
</id>
<property name="parent_field_1" column="parent_field_1" type="java.lang.String" not-null="false" />
<property name="parent_field_2" column="parent_field_2" type="java.lang.String" not-null="false" />
<!-- *** RELATION AVEC L OBJET CHILD *** -->
<bag name="children" inverse="true" fetch="select" cascade="save-update,persist,merge,delete,all-delete-orphan" lazy="false">
<key column="ID_PARENT" />
<one-to-many class="Child" />
</bag>
</class>
</hibernate-mapping>
Code:
<?xml version="1.0" encoding='UTF-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
<hibernate-mapping package="test.clone">
<class name="Child" table="CHILD">
<id name="id" column="ID_CHILD" type="java.lang.Integer">
<generator class="native">
<!-- param name="sequence">S_PFA_MODIFICATION_REF</param-->
</generator>
</id>
<property name="child_field_1" column="child_field_1" type="java.lang.String" not-null="false" />
<property name="child_field_2" column="child_field_2" type="java.lang.String" not-null="false" />
<!-- *** RELATION AVEC L OBJET PARENT *** -->
<many-to-one column="ID_PARENT" name="parent" class="Parent" />
</class>
</hibernate-mapping>
Code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- ========================= Start of PERSISTENCE DEFINITIONS ========================= -->
<bean id="mySQLDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/clone"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!-- bean id="oracleDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:PFA"/>
<property name="username" value="pf_test"/>
<property name="password" value="pf_test"/>
</bean-->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="mySQLDataSource"/>
<property name="mappingResources">
<list>
<value>test/clone/Parent.hbm.xml</value>
<value>test/clone/Child.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">create</prop>
</props>
</property>
<!-- property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.OracleDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">create</prop>
</props>
</property-->
</bean>
<!-- DAO object: Hibernate implementation -->
<bean id="DAO" class="test.clone.DAO">
<property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>
<!-- Add more services here -->
</beans>
My configuration:
JUnit
Spring
Hibernate 3.1
Oracle or MySQL data base
Thank Stef