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 |
From: Steve E. (JIRA) <no...@at...> - 2006-01-28 11:48:41
|
[ http://opensource.atlassian.com/projects/hibernate/browse/HHH-1401?page=all ] Steve Ebersole updated HHH-1401: -------------------------------- Component: core > 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 > Components: core > 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 |
From: David T. (JIRA) <no...@at...> - 2006-02-01 08:35:41
|
[ http://opensource.atlassian.com/projects/hibernate/browse/HHH-1401?page=comments#action_21951 ] David Trott commented on HHH-1401: ---------------------------------- I have been able to narrow this issue down a little further: The extra updates are **NOT** issued if I remove the mapping for versioning: <version name="concurrentVersion" column="concurrent_version" type="long"/> > 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 > Components: core > 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 |
From: Josh M. (JIRA) <no...@at...> - 2006-04-19 19:47:29
|
[ http://opensource.atlassian.com/projects/hibernate/browse/HHH-1401?page=comments#action_22778 ] Josh Moore commented on HHH-1401: --------------------------------- Hibernate 3.1.3 Postgres 7.4.8 Java 1.5.0_06 I fairly certain I'm having the same issue. Code of the form fails: // first session Experimenter e = (Experimenter) session.get(Experimenter.class, 0); expVersion = e.getVersion(); Project p = new Project(); p.getDetails().setOwner( e ); p = session.merge( p ); Long id = p.getId(); // second session p = (Project) session.createQuery("from Project p " + " join fetch p.details.owner " + " where p.id = :id "). setParameter("id",id ).uniqueResult(); p.setName( " updated " ); p = (Project) session.merge( p ); // third session Experimenter e = (Experimenter) session.get(Experimenter.class, 0); assertTrue(expVersion.equals(e.getVersion())); Preparing a test case now. TypeFactory.replace seems to be setting the "dirty=true" in AbstractPersistentCollection by calling clear on PersistentSet which calls dirty(). (Will attach a screen shot momentarily) > 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 > Components: core > 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 |
From: Josh M. (JIRA) <no...@at...> - 2006-04-19 19:49:29
|
[ http://opensource.atlassian.com/projects/hibernate/browse/HHH-1401?page=all ] Josh Moore updated HHH-1401: ---------------------------- Attachment: Screenshot-Debug - AbstractPersistentCollection.class - Eclipse SDK .png Screenshot showing the call to AbstractPersistentCollection.dirty() in Eclipse debugger. > 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 > Components: core > Versions: 3.1.1 > Environment: Hibernate 3.1.1 > Postgres 8.03 > Java 1.4.2_09 > Reporter: David Trott > Attachments: Screenshot-Debug - AbstractPersistentCollection.class - Eclipse SDK .png > > > 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 |
From: Josh M. (JIRA) <no...@at...> - 2006-04-19 20:33:28
|
[ http://opensource.atlassian.com/projects/hibernate/browse/HHH-1401?page=all ] Josh Moore updated HHH-1401: ---------------------------- Attachment: HHH-1401.zip hhh1401 directory with test case, two java beans, two hbm files to be copied to <HIB>/test/org/hibernate/test. Output of intersted (with show_sql=true) is: Hibernate: select nextval ('hibernate_sequence') Hibernate: insert into experimenter (version, id) values (?, ?) Hibernate: select experiment0_.id as id0_1_, experiment0_.version as version0_1_, groupexper1_.child as child3_, groupexper1_.id as id3_, groupexper1_.id as id1_0_, groupexper1_.version as version1_0_, groupexper1_.child as child1_0_ from experimenter experiment0_ left outer join groupexperimentermap groupexper1_ on experiment0_.id=groupexper1_.child where experiment0_.id=? Hibernate: update experimenter set version=? where id=? and version=? This final update is unnecessary since we are simply merging an extant Experimenter: // Create our data Session s = openSession(); Transaction t = s.beginTransaction(); Experimenter e = new Experimenter(); e = (Experimenter) s.merge( e ); s.flush(); t.commit(); s.close(); // Do our problematic merge. s = openSession(); t = s.beginTransaction(); Experimenter e2 = (Experimenter) s.merge( e ); s.flush(); t.commit(); s.close(); assertTrue( "Version was incremented although we did nothing", e.getVersion().equals( e2.getVersion() ) ); > 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 > Components: core > Versions: 3.1.1 > Environment: Hibernate 3.1.1 > Postgres 8.03 > Java 1.4.2_09 > Reporter: David Trott > Attachments: HHH-1401.zip, Screenshot-Debug - AbstractPersistentCollection.class - Eclipse SDK .png > > > 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 |
From: Josh M. (JIRA) <no...@at...> - 2006-04-20 07:35:29
|
[ http://opensource.atlassian.com/projects/hibernate/browse/HHH-1401?page=comments#action_22781 ] Josh Moore commented on HHH-1401: --------------------------------- I can also confirm that removing the <version/> mappings prevents the unnecessary updates. Output of interest (and not "intersted") is: Hibernate: select nextval ('hibernate_sequence') Hibernate: insert into experimenter (id) values (?) Hibernate: select experiment0_.id as id0_1_, 0 as formula0_1_, groupexper1_.child as child3_, groupexper1_.id as id3_, groupexper1_.id as id1_0_, groupexper1_.child as child1_0_, 0 as formula1_0_ from experimenter experiment0_ left outer join groupexperimentermap groupexper1_ on experiment0_.id=groupexper1_.child where experiment0_.id=? > 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 > Components: core > Versions: 3.1.1 > Environment: Hibernate 3.1.1 > Postgres 8.03 > Java 1.4.2_09 > Reporter: David Trott > Attachments: HHH-1401.zip, Screenshot-Debug - AbstractPersistentCollection.class - Eclipse SDK .png > > > 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 |
From: Josh M. (JIRA) <no...@at...> - 2006-07-19 12:59:00
|
[ http://opensource.atlassian.com/projects/hibernate/browse/HHH-1401?page=comments#action_23665 ] Josh Moore commented on HHH-1401: --------------------------------- Confirmed test failure for: Revision: 10123 (~3.2.cr2) HQL dialect. > 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 > Components: core > Versions: 3.1.1 > Environment: Hibernate 3.1.1 > Postgres 8.03 > Java 1.4.2_09 > Reporter: David Trott > Attachments: HHH-1401.zip, Screenshot-Debug - AbstractPersistentCollection.class - Eclipse SDK .png > > > 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 |
From: Josh M. (JIRA) <no...@at...> - 2006-07-19 13:06:59
|
[ http://opensource.atlassian.com/projects/hibernate/browse/HHH-1401?page=all ] Josh Moore updated HHH-1401: ---------------------------- Attachment: TEST-org.hibernate.test.optlock.OptimisticLockTest.txt These tests failed as well: [junit] TEST org.hibernate.test.cuk.CompositePropertyRefTest FAILED [junit] TEST org.hibernate.test.hql.ASTParserLoadingTest FAILED [junit] TEST org.hibernate.test.hql.CriteriaHQLAlignmentTest FAILED [junit] TEST org.hibernate.test.hql.HQLTest FAILED [junit] TEST org.hibernate.test.optlock.OptimisticLockTest FAILED [junit] TEST org.hibernate.test.orphan.PropertyRefTest FAILED [junit] TEST org.hibernate.test.propertyref.PropertyRefTest FAILED [junit] TEST org.hibernate.test.readonly.ReadOnlyTest FAILED [junit] TEST org.hibernate.test.sql.GeneralTest FAILED OptimisticLock, naturally, seems to be significant. Attaching the test log here. > 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 > Components: core > Versions: 3.1.1 > Environment: Hibernate 3.1.1 > Postgres 8.03 > Java 1.4.2_09 > Reporter: David Trott > Attachments: HHH-1401.zip, Screenshot-Debug - AbstractPersistentCollection.class - Eclipse SDK .png, TEST-org.hibernate.test.optlock.OptimisticLockTest.txt > > > 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 |