[hibernate-issues] [Hibernate-JIRA] Updated: (HHH-1401) session.merge() executes unnecessary updates when one-to-many relationship is defined.
Steve Ebersole (JIRA)
noreply at atlassian.com
Thu Aug 31 06:27:26 EDT 2006
[ http://opensource.atlassian.com/projects/hibernate/browse/HHH-1401?page=all ]
Steve Ebersole updated HHH-1401:
--------------------------------
Fix Version: 3.2.1
Assign To: Steve Ebersole
> 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, 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