From: David T. (JIRA) <no...@at...> - 2006-01-27 06:49:42
|
session.merge() executes unnecessary updates when one-to-many relationship is defined. -------------------------------------------------------------------------------------- Key: HHH-1401 URL: http://opensource.atlassian.com/projects/hibernate/browse/HHH-1401 Project: Hibernate3 Type: Bug Versions: 3.1.1 Environment: Hibernate 3.1.1 Postgres 8.03 Java 1.4.2_09 Reporter: David Trott 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). 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 hibernate still runs updates on A,B and C however not on D (D has no one-to-many relationships). I am including the code and hibernate mapping for B as it is representative of the code for all the other classes. I am no expect on the hibernate implementation, but my suspicion of the cause is when hibernate substitutes a PersistentBag for the ArrayList in the merged object (associated with the session) it detects this as a change and hence triggers the update, unfortunately the data itself has not changed hence no update is necessary. FYI: If I change the initialization of the bags from "new ArrayList()" to "new PersistentBag()" the extra updates go away, however then it doesn't save real changes correctly. 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(); } } ****************************** *** Mapping Document **** ****************************** <?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> ************************* *** Generated SQL **** ************************* 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 ** ************************** *** Accessing 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); -- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://opensource.atlassian.com/projects/hibernate/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira |