session.merge() executes unnecessary updates when one-to-many relationship is defined.

Description

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);

Attachments

4
  • 13 Sep 2006, 10:24 AM
  • 19 Jul 2006, 03:05 PM
  • 19 Apr 2006, 10:31 PM
  • 19 Apr 2006, 09:48 PM

Activity

Show:

Steve Ebersole March 21, 2011 at 7:04 PM

Bulk closing stale resolved issues

Steve Ebersole November 7, 2006 at 12:05 AM

So at this point, I have added a bunch of tests now to test this behavior. If anyone is still experiencing issues here, please refer to one of these tests as a baseline for the behavior you see...

The tests consist of a couple new test methods in org.hibernate.test.ops.MergeTest:
(1) testNoExtraUpdatesOnMerge()
(2) testNoExtraUpdatesOnMergeWithCollection()
(3) testNoExtraUpdatesOnMergeVersioned()
(4) testNoExtraUpdatesOnMergeVersionedWithCollection()

and the new/expanded org.hibernate.test.collection.CollectionSuite

For those previously experiencing issues in this area, please check out SVN (3.2 or trunk) and test with my changes...

David Trott November 6, 2006 at 11:53 PM

All the cases I tested were consistent with Steve Ebersole's test case (I am not aware of any other ones).

Steve Ebersole November 3, 2006 at 8:44 PM

So in my testing (against the latest code), the only scenario in which I can reproduce this (I am using the entity's defined in org.hhibernate.test.ops) is when merging an unmodified entity which:
(1) is versioned
(2) has collection which actually has elements

I am still looking into that case.

Can anyone else confirm that they do not see this problem in any other scenarios? The collection I am using is specifically an inverse hierarchical Set...

Josh Moore September 13, 2006 at 10:24 AM

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

Fixed

Details

Assignee

Reporter

Components

Fix versions

Affects versions

Priority

Created January 27, 2006 at 8:49 AM
Updated March 21, 2011 at 7:04 PM
Resolved November 7, 2006 at 12:18 AM

Flag notifications