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 |