[hibernate-issues] [Hibernate-JIRA] Updated: (HHH-1401) session.merge() executes unnecessary updates when one-to-many relationship is defined.

Josh Moore (JIRA) noreply at atlassian.com
Wed Sep 13 04:25:25 EDT 2006


     [ http://opensource.atlassian.com/projects/hibernate/browse/HHH-1401?page=all ]

Josh Moore updated HHH-1401:
----------------------------

    Attachment: HHH1401Test.java

Attached new HHH1401Test (replaces version in zip). Has second test using collections which fail despite the fixes Steve made to HHH-1668.

> 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
>     Assignee: Steve Ebersole
>      Fix For: 3.2.1
>  Attachments: HHH-1401.zip, HHH1401Test.java, 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




More information about the hibernate-issues mailing list