Hibernate version: 3.1.1
Mapping documents: See Below
Code between sessionFactory.openSession() and session.close(): session.merge(a);
Name and version of the database you are using: Postgres 8.0.3
The generated SQL (show_sql=true): See Below
I am attempting to use the session.merge() functionality in order to synchronize the state of the data coming from the web tier with the database, however I am seeing unnecessary updates (when nothing has changed), therefore I am trying to track down the cause.
In order to track down the problem I created a test case with four tables and four classes (A,B,C and D)
Where:
A is the parent of B.
B is the parent of C.
C is the parent of D.
And there are no other relationships present.
All these relationships are bi-directional with the one-to-many side marked as inverse="true" and cascade="all-delete-orphan".
The merge() is working fine (the data gets updated correctly) except that when there is no change to the data it still runs updates on A,B and C however strangely not on D.
I am including the code and hibernate mapping for B as it is representative of the code for all the other classes.
Does anyone have any idea how to stop these phantom updates from occurring?
TIA.
David
PS If I change the initialization of the bags from "new ArrayList()" to "new PersistentBag()" the extra updates go away, however it doesn't save changes correctly.
Code:
package com.mycompany.dal.transfer.impl;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.mycompany.dal.transfer.interfaces.ADTO;
import com.mycompany.dal.transfer.interfaces.BDTO;
import com.mycompany.dal.transfer.interfaces.CDTO;
import org.apache.commons.collections.Closure;
import org.apache.commons.collections.CollectionUtils;
public class BDTOImpl implements BDTO {
public BDTOImpl () {
}
private Long bId;
public Long getBId() {
return bId;
}
public void setBId(Long bId) {
this.bId = bId;
}
private Long concurrentVersion;
public Long getConcurrentVersion() {
return concurrentVersion;
}
public void setConcurrentVersion(Long concurrentVersion) {
this.concurrentVersion = concurrentVersion;
}
// Package level protection so that overrides can access it.
boolean deleting = false;
private String name;
/**
* Returns the Name.
*
* @return String - The Name
*/
public String getName() {
return name;
}
/**
* Set the Name.
*
* @param name String - The Name.
*/
public void setName(String name) {
this.name = name;
}
private ADTO a;
/**
* Returns the A.
*
* @return ADTO - The A.
*/
public ADTO getA() {
return a;
}
public ADTO getAInternal() {
return a;
}
/**
* Updates the A.
*
* @param a - ADTO The A.
*/
public void setA(ADTO a) {
if (this.a == a) {
return;
}
if (this.a != null) {
((ADTOImpl) this.a).removeBInternal(this);
}
this.a = a;
if (a != null) {
((ADTOImpl) a).addBInternal(this);
}
}
public void setAInternal(ADTO a) {
if (deleting) {
return;
}
if (this.a != a &&
this.a != null && a != null) {
throw new IllegalStateException("BDTO cannot be a member of two A collections: " + toString());
}
this.a = a;
}
private List cs;
private List csMutable;
{ setCsMutable(new ArrayList()); }
public List getCsMutable() {
return csMutable;
}
public void setCsMutable(List cs) {
this.cs = Collections.unmodifiableList(cs);
this.csMutable = cs;
}
public List getCs() {
return cs;
}
public void addC(CDTO c) {
csMutable.add(c);
((CDTOImpl) c).setBInternal(this);
}
public void addCInternal(CDTO c) {
csMutable.add(c);
}
public void removeC(CDTO c) {
csMutable.remove(c);
((CDTOImpl) c).setBInternal(null);
}
public void removeCInternal(CDTO c) {
if (!deleting) {
csMutable.remove(c);
}
}
public void beforeDelete() {
// Guard to prevent infinite loop.
if (deleting) {
return;
}
deleting = true;
if (this.a != null) {
((ADTOImpl) this.a).removeBInternal(this);
}
CollectionUtils.forAllDo(new ArrayList(csMutable), new Closure() {
public void execute(Object ob) {
((CDTOImpl) ob).beforeDelete();
}
});
}
public int hashCode() {
return (new HashCodeBuilder(17,37)
.append(getBId())
).toHashCode();
}
public boolean equals(Object o) {
boolean equals = false;
if (o != null && o instanceof BDTO) {
BDTO other = (BDTO) o;
return (new EqualsBuilder()
.append(getBId(), other.getBId())
).isEquals();
}
return equals;
}
public String toString() {
return new ToStringBuilder(this)
.append("bId", getBId())
.append("name", getName())
.toString();
}
}
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="com.mycompany.dal.transfer.impl" auto-import="true">
<class name="com.mycompany.dal.transfer.impl.BDTOImpl" table="b">
<id name="BId" type="long">
<column name="b_id" not-null="true"/>
<generator class="native"/>
</id>
<version name="concurrentVersion" column="concurrent_version" type="long"/>
<property name="Name" type="string">
<column name="name" length="60" not-null="false"/>
</property>
<many-to-one name="AInternal" class="com.mycompany.dal.transfer.impl.ADTOImpl">
<column name="a_id" not-null="true"/>
</many-to-one>
<bag name="CsMutable" cascade="all-delete-orphan" inverse="true">
<key>
<column name="b_id" not-null="true"/>
</key>
<one-to-many class="com.mycompany.dal.transfer.impl.CDTOImpl"/>
</bag>
</class>
</hibernate-mapping>
Code:
05:35:39,887 INFO [STDOUT] Hibernate: select adtoimpl0_.a_id as a1_162_2_, adtoimpl0_.concurrent_version as concurrent2_162_2_, adtoimpl0_.name as name162_2_, bsmutable1_.a_id as a4_4_, bsmutable1_.b_id as b1_4_, bsmutable1_.b_id as b1_164_0_, bsmutable1_.concurrent_version as concurrent2_164_0_, bsmutable1_.name as name164_0_, bsmutable1_.a_id as a4_164_0_, csmutable2_.b_id as b4_5_, csmutable2_.c_id as c1_5_, csmutable2_.c_id as c1_165_1_, csmutable2_.concurrent_version as concurrent2_165_1_, csmutable2_.name as name165_1_, csmutable2_.b_id as b4_165_1_ from a adtoimpl0_ left outer join b bsmutable1_ on adtoimpl0_.a_id=bsmutable1_.a_id left outer join c csmutable2_ on bsmutable1_.b_id=csmutable2_.b_id where adtoimpl0_.a_id=?
05:35:39,992 INFO [STDOUT] Hibernate: select ddtoimpl0_.d_id as d1_168_0_, ddtoimpl0_.concurrent_version as concurrent2_168_0_, ddtoimpl0_.name as name168_0_, ddtoimpl0_.c_id as c4_168_0_ from d ddtoimpl0_ where ddtoimpl0_.d_id=?
05:35:40,007 INFO [STDOUT] Hibernate: select dsmutable0_.c_id as c4_1_, dsmutable0_.d_id as d1_1_, dsmutable0_.d_id as d1_168_0_, dsmutable0_.concurrent_version as concurrent2_168_0_, dsmutable0_.name as name168_0_, dsmutable0_.c_id as c4_168_0_ from d dsmutable0_ where dsmutable0_.c_id=?
*** Start Extra Updates **
05:35:40,030 INFO [STDOUT] Hibernate: update b set concurrent_version=?, name=?, a_id=? where b_id=? and concurrent_version=?
05:35:40,038 INFO [STDOUT] Hibernate: update c set concurrent_version=?, name=?, b_id=? where c_id=? and concurrent_version=?
05:35:40,044 INFO [STDOUT] Hibernate: update a set concurrent_version=?, name=? where a_id=? and concurrent_version=?
*** End Extra Updates **
Code:
DataAccessLayer dal = DataAccessLayerBuilder.getInstance();
ADAO aDAO = dal.getADAO();
BDAO bDAO = dal.getBDAO();
CDAO cDAO = dal.getCDAO();
DDAO dDAO = dal.getDDAO();
ADTO a = aDAO.newA();
a.setAId(new Long(1));
a.setConcurrentVersion(new Long(0));
a.setName("A");
BDTO b = bDAO.newB();
CDTO c = cDAO.newC();
DDTO d = dDAO.newD();
b.setBId(new Long(2));
c.setCId(new Long(3));
d.setDId(new Long(4));
b.setConcurrentVersion(new Long(0));
c.setConcurrentVersion(new Long(0));
d.setConcurrentVersion(new Long(0));
b.setName("B");
c.setName("C");
d.setName("D");
b.setA(a);
c.setB(b);
d.setC(c);
aDAO.mergeA(a);