These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 2 posts ] 
Author Message
 Post subject: Hibernate issuing updates when collection is not modified
PostPosted: Mon Apr 25, 2011 1:35 pm 
Newbie

Joined: Mon Apr 25, 2011 11:29 am
Posts: 1
We have a simple parent-child relationship, to prevent calling code from modifying the relationship, we are using google collections -ImmutableSet.copyOf on get method.
Code:
public Set<OrganizationalUnit> getChildren()
   {
      return ImmutableSet.copyOf(children);
   }


Though nothing is modified in the flow , hibernate is issuing update statements. Can any one explain whats happening behind the scenes and why hibernate is considering the collection dirty when we are just creating a copy of the collection retrieved from DB.

Below are the hbm files, mapping classes and corresponding test case

OrganizationalUnitCatalog.java

Code:
package com.test.domain.product;

import java.util.Collection;
import java.util.List;
import java.util.Set;

import org.springframework.util.CollectionUtils;

import com.google.common.base.Function;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

public class OrganizationalUnitCatalog
{
   private Long id;

   private Long systemId;

   private String code;

   private Set<OrganizationalUnit> children;
   

   

   public Set<OrganizationalUnit> getChildren()
   {
      return ImmutableSet.copyOf(children);
   }

   public void setChildren(final Set<OrganizationalUnit> products)
   {
      this.children = products;
   }

   public Long getSystemId()
   {
      return systemId;
   }

   public void setSystemId(final Long systemId)
   {
      this.systemId = systemId;
   }

   public Long getId()
   {
      return id;
   }

   public void setId(final Long id)
   {
      this.id = id;
   }

   @Override
   public String toString()
   {
      return String.format("OrganizationalUnitCatalog [id=%s, systemId=%s, products=%s]", id, systemId, children.size());
   }

   public void setCode(final String code)
   {
      this.code = code;
   }

   public String getCode()
   {
      return code;
   }

   @Override
   public int hashCode()
   {
      final int prime = 31;
      int result = 1;
      result = prime * result + ((code == null) ? 0 : code.hashCode());
      result = prime * result + ((systemId == null) ? 0 : systemId.hashCode());
      return result;
   }

   @Override
   public boolean equals(final Object obj)
   {
      if (this == obj)
      {
         return true;
      }
      if (obj == null)
      {
         return false;
      }
      if (getClass() != obj.getClass())
      {
         return false;
      }
      OrganizationalUnitCatalog other = (OrganizationalUnitCatalog) obj;
      if (code == null)
      {
         if (other.code != null)
         {
            return false;
         }
      }
      else if (!code.equals(other.code))
      {
         return false;
      }
      if (systemId == null)
      {
         if (other.systemId != null)
         {
            return false;
         }
      }
      else if (!systemId.equals(other.systemId))
      {
         return false;
      }
      return true;
   }

}


OrganizationalUnitCatalog.hbm.xml
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
                                   "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="com.test.domain.product">
   <class name="OrganizationalUnitCatalog" table="ORGANIZATIONALUNITCATALOG">
   <cache usage="read-write" />
      <id name="id" type="java.lang.Long" column="ID">
         <generator class="native">
            <param name="sequence">ORGANIZATIONALUNITCATALOG_SN</param>
         </generator>
      </id>
      <property name="systemId" type="java.lang.Long" column="SYSTEMID" not-null="true" />
      <property name="code" type="java.lang.String" column="CODE" not-null="true" />
      <set lazy="false" name="children" table="ORGANIZATIONALUNIT" cascade="all" fetch="join" >
         <cache usage="read-write" />
         <key>
            <column name="ORGANIZATIONALUNITCATALOG_ID" />
         </key>
         <one-to-many class="OrganizationalUnit" />
      </set>
   </class>
   <query name="HibernateOrganizationalUnitCatalogDao.findBySystemId">from OrganizationalUnitCatalog where systemId = :systemId</query>
</hibernate-mapping>


OrganizationalUnit mapping class

Code:
package com.test.domain.product;

import java.util.Collections;
import java.util.Date;
import java.util.Set;
import java.util.Stack;

import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;


public class OrganizationalUnit
{
   public static final String QUALIFIED_NAME_SEPARATOR = "/";

   private static final Joiner JOINER = Joiner.on(QUALIFIED_NAME_SEPARATOR);

   private Long id;

   private OrganizationalUnit parent;

   private Set<OrganizationalUnit> children = Collections.emptySet();
   

   private String code;

   private String name;

   private Date startDate;

   private Date endDate;

   private boolean decisionable;

   private boolean selectable;

   private String qualifiedCode;

   
   public void addChild(final OrganizationalUnit child)
   {
      /*
       * Preconditions.checkState(systemId != null,
       * "Set the systemId before adding children. This ensures all children have the systemId when persisted.");
       */
      if (children.isEmpty())
      {
         children = Sets.newHashSet();
      }
      child.setParent(this);
      children.add(child);
   }

   
   public String getQualifiedCode()
   {
      if (qualifiedCode != null)
      {
         // use the cache
         return qualifiedCode;
      }
      if (parent == null)
      {
         qualifiedCode = code;
         return qualifiedCode;
      }

      Stack<String> s = new Stack<String>();
      OrganizationalUnit p = parent;
      while (p != null)
      {
         s.push(p.getCode());
         p = p.getParent();
      }
      qualifiedCode = JOINER.join(s) + QUALIFIED_NAME_SEPARATOR + code;

      return qualifiedCode;
   }

   public Long getId()
   {
      return id;
   }

   public void setId(final Long id)
   {
      this.id = id;
   }

   public String getCode()
   {
      return code;
   }

   public void setCode(final String code)
   {
      this.code = code;
   }

   public String getName()
   {
      return name;
   }

   public void setName(final String name)
   {
      this.name = name;
   }

   public Date getStartDate()
   {
      return startDate;
   }

   public void setStartDate(final Date startDate)
   {
      this.startDate = startDate;
   }

   public Date getEndDate()
   {
      return endDate;
   }

   public void setEndDate(final Date endDate)
   {
      this.endDate = endDate;
   }

   public boolean isDecisionable()
   {
      return decisionable;
   }

   public void setDecisionable(final boolean isDecisionable)
   {
      this.decisionable = isDecisionable;
   }

   public boolean isSelectable()
   {
      return selectable;
   }

   public void setSelectable(final boolean isSelectable)
   {
      this.selectable = isSelectable;
   }

   public OrganizationalUnit getParent()
   {
      return parent;
   }

   public void setParent(final OrganizationalUnit parent)
   {
      this.parent = parent;
   }

   public Set<OrganizationalUnit> getChildren()
   {
      return ImmutableSet.copyOf(children);
   }

   public void setChildren(final Set<OrganizationalUnit> children)
   {
      this.children = children;
   }

   public void setQualifiedCode(final String qCode)
   {
      this.qualifiedCode = qCode;
   }

   public Set<ZipCodePreferenceEntry> getBureauPreferences()
   {
      return bureauPreferences;
   }

   public void setBureauPreferences(final Set<ZipCodePreferenceEntry> bureauPreferences)
   {
      this.bureauPreferences = bureauPreferences;
   }

   @Override
   public String toString()
   {
      return String
            .format("OrganizationalUnit [getCode=%s, getQualifiedCode=%s, getName=%s, getParent=%s, getChildren=%s, getStartDate=%s, getEndDate=%s, isDecisionable=%s, isSelectable=%s, getId=%s]",
                  getCode(), getQualifiedCode(), getName(), getParent(), getChildren().size(), getStartDate(), getEndDate(),
                  isDecisionable(), isSelectable(), getId());
   }

   @Override
   public int hashCode()
   {
      final int prime = 31;
      int result = 1;
      result = prime * result + ((getQualifiedCode() == null) ? 0 : getQualifiedCode().hashCode());
      return result;
   }

   @Override
   public boolean equals(final Object obj)
   {
      if (this == obj)
      {
         return true;
      }
      if (obj == null)
      {
         return false;
      }
      if (getClass() != obj.getClass())
      {
         return false;
      }
      OrganizationalUnit other = (OrganizationalUnit) obj;
      if (getQualifiedCode() == null)
      {
         if (other.getQualifiedCode() != null)
         {
            return false;
         }
      }
      else if (!getQualifiedCode().equals(other.getQualifiedCode()))
      {
         return false;
      }
      return true;
   }
}


OrganizationalUnit.hbm.xml

Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
                                   "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.test.domain.product">
   <class name="OrganizationalUnit" table="ORGANIZATIONALUNIT">
      <cache usage="read-write" />
      <id name="id" type="java.lang.Long" column="ID">
         <generator class="native">
            <param name="sequence">ORGANIZATIONALUNIT_SN</param>
         </generator>
      </id>
      <many-to-one name="parent" class="OrganizationalUnit" lazy="false" column="PARENT" />
      <set name="children" lazy="false" fetch="join" table="ORGANIZATIONALUNIT" cascade="all">
         <cache usage="read-write" />
         <key>
            <column name="PARENT" />
         </key>
         <one-to-many class="OrganizationalUnit" />
      </set>      
      <property name="code" type="java.lang.String" column="CODE" not-null="true" />
      <property name="name" type="java.lang.String" column="NAME" />
      <property name="qualifiedCode" type="java.lang.String" column="QUALIFIEDCODE" />
      <property name="startDate" type="java.util.Date" column="STARTDATE" />
      <property name="endDate" type="java.util.Date" column="ENDDATE" />
      <property name="decisionable" type="boolean" column="ISDECISIONABLE" />
      <property name="selectable" type="boolean" column="ISSELECTABLE" />
   </class>
</hibernate-mapping>


Test Case
Code:
package com.equifax.ic.platform.domain.product;

import java.sql.SQLException;
import java.util.List;
import java.util.Set;

import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.HSQLDialect;
import org.junit.Before;
import org.junit.Test;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.orm.hibernate3.HibernateTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

public class Test1
{
   private OrganizationalUnitCatalog catalog;

   private OrganizationalUnit mortgage;

   protected HibernateTemplate hibernateTemplate;

   protected TransactionTemplate transTemplate;

   protected PlatformTransactionManager transactionManager;

   @Before
   public void init()
   {
      catalog = new OrganizationalUnitCatalog();
      catalog.setCode("test");
      catalog.setSystemId(Long.valueOf(0));

      mortgage = new OrganizationalUnit();
      mortgage.setCode("Mortgage");
      OrganizationalUnit ml = new OrganizationalUnit();
      ml.setCode("Mortgage Loan");
      OrganizationalUnit me = new OrganizationalUnit();
      me.setCode("Home Equity LOC");

      mortgage.addChild(me);
      mortgage.addChild(ml);
      // add unit to catalog
      catalog.setChildren(Sets.newHashSet(mortgage));
   }

   @Test
   public void updateIssue()
   {
      hibernateTemplate.save(catalog);

      hibernateTemplate.execute(new HibernateCallback() {
         public Object doInHibernate(Session session) throws HibernateException, SQLException
         {
            Transaction tx = session.beginTransaction();
            Query query = session
                  .createQuery("from com.test.domain.product.OrganizationalUnitCatalog ouc where ouc.systemId=0");
            query.setMaxResults(1);
            OrganizationalUnitCatalog entity = (OrganizationalUnitCatalog) query.list().get(0);
            Set<OrganizationalUnit> children = entity.getChildren();
            System.out.println("Children count :" + children.size());

            tx.commit();

            return null;
         }
      });
   }

   @Before
   public void setUp() throws Exception
   {
      Configuration configuration = new Configuration();
      configuration.setProperty(Environment.DRIVER, getDriverClassName());
      configuration.setProperty(Environment.URL, getJdbcUrl());
      configuration.setProperty(Environment.USER, getuserName());
      configuration.setProperty(Environment.PASS, getPassword());
      configuration.setProperty(Environment.DIALECT, getHibernateDialect());
      configuration.setProperty(Environment.SHOW_SQL, "true");
      configuration.setProperty(Environment.HBM2DDL_AUTO, getHbm2DdlAuto());
      configuration.setProperty(Environment.STATEMENT_BATCH_SIZE, "5");
      configuration.setProperty(Environment.USE_SECOND_LEVEL_CACHE, "true");
      configuration.setProperty(Environment.USE_QUERY_CACHE, "true");
      configuration.setProperty(Environment.CACHE_PROVIDER, "org.hibernate.cache.EhCacheProvider");

      for (String resource : getHbmResourceUnderTest())
      {
         configuration.addResource(resource);
      }
      SessionFactory sessionFactory = configuration.buildSessionFactory();
      // OracleLobHandler lobHandler = new OracleLobHandler();
      // lobHandler.setNativeJdbcExtractor(new SimpleNativeJdbcExtractor());
      //
      // ((LocalSessionFactoryBean) sessionFactory).setLobHandler(lobHandler);

      transactionManager = new HibernateTransactionManager(sessionFactory);
      hibernateTemplate = new HibernateTemplate(sessionFactory);
      transTemplate = new TransactionTemplate(transactionManager);
   }

   protected String getHbm2DdlAuto()
   {
      return "create-drop";
   }

   protected String getPassword()
   {
      return "";
   }

   protected String getHibernateDialect()
   {
      return HSQLDialect.class.getName();
   }

   protected String getuserName()
   {
      return "sa";
   }

   protected String getJdbcUrl()
   {
      return "jdbc:hsqldb:mem:test";
   }

   protected String getDriverClassName()
   {
      return "org.hsqldb.jdbcDriver";
   }

   public List<String> getHbmResourceUnderTest()
   {
      return Lists
            .newArrayList("com/test/domain/OrganizationalUnit.hbm.xml", "com/test/domain/OrganizationalUnitCatalog.hbm.xml");
   }
}


Test Output
Code:
Hibernate: insert into ORGANIZATIONALUNITCATALOG (ID, SYSTEMID, CODE) values (null, ?, ?)
Hibernate: call identity()
Hibernate: insert into ORGANIZATIONALUNIT (ID, PARENT, CODE, NAME, QUALIFIEDCODE, STARTDATE, ENDDATE, ISDECISIONABLE, ISSELECTABLE) values (null, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: call identity()
Hibernate: insert into ORGANIZATIONALUNIT (ID, PARENT, CODE, NAME, QUALIFIEDCODE, STARTDATE, ENDDATE, ISDECISIONABLE, ISSELECTABLE) values (null, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: call identity()
Hibernate: insert into ORGANIZATIONALUNIT (ID, PARENT, CODE, NAME, QUALIFIEDCODE, STARTDATE, ENDDATE, ISDECISIONABLE, ISSELECTABLE) values (null, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: call identity()
Hibernate: update ORGANIZATIONALUNIT set ORGANIZATIONALUNITCATALOG_ID=? where ID=?
Hibernate: update ORGANIZATIONALUNIT set PARENT=? where ID=?
Hibernate: update ORGANIZATIONALUNIT set PARENT=? where ID=?
Hibernate: select top ? organizati0_.ID as ID1_, organizati0_.SYSTEMID as SYSTEMID1_, organizati0_.CODE as CODE1_ from ORGANIZATIONALUNITCATALOG organizati0_ where organizati0_.SYSTEMID=0
Hibernate: select children0_.ORGANIZATIONALUNITCATALOG_ID as ORGANIZ10_1_, children0_.ID as ID1_, children0_.ID as ID0_0_, children0_.PARENT as PARENT0_0_, children0_.CODE as CODE0_0_, children0_.NAME as NAME0_0_, children0_.QUALIFIEDCODE as QUALIFIE5_0_0_, children0_.STARTDATE as STARTDATE0_0_, children0_.ENDDATE as ENDDATE0_0_, children0_.ISDECISIONABLE as ISDECISI8_0_0_, children0_.ISSELECTABLE as ISSELECT9_0_0_ from ORGANIZATIONALUNIT children0_ where children0_.ORGANIZATIONALUNITCATALOG_ID=?
Hibernate: select children0_.PARENT as PARENT1_, children0_.ID as ID1_, children0_.ID as ID0_0_, children0_.PARENT as PARENT0_0_, children0_.CODE as CODE0_0_, children0_.NAME as NAME0_0_, children0_.QUALIFIEDCODE as QUALIFIE5_0_0_, children0_.STARTDATE as STARTDATE0_0_, children0_.ENDDATE as ENDDATE0_0_, children0_.ISDECISIONABLE as ISDECISI8_0_0_, children0_.ISSELECTABLE as ISSELECT9_0_0_ from ORGANIZATIONALUNIT children0_ where children0_.PARENT=?
Hibernate: select children0_.PARENT as PARENT1_, children0_.ID as ID1_, children0_.ID as ID0_0_, children0_.PARENT as PARENT0_0_, children0_.CODE as CODE0_0_, children0_.NAME as NAME0_0_, children0_.QUALIFIEDCODE as QUALIFIE5_0_0_, children0_.STARTDATE as STARTDATE0_0_, children0_.ENDDATE as ENDDATE0_0_, children0_.ISDECISIONABLE as ISDECISI8_0_0_, children0_.ISSELECTABLE as ISSELECT9_0_0_ from ORGANIZATIONALUNIT children0_ where children0_.PARENT=?

Hibernate: select children0_.PARENT as PARENT1_, children0_.ID as ID1_, children0_.ID as ID0_0_, children0_.PARENT as PARENT0_0_, children0_.CODE as CODE0_0_, children0_.NAME as NAME0_0_, children0_.QUALIFIEDCODE as QUALIFIE5_0_0_, children0_.STARTDATE as STARTDATE0_0_, children0_.ENDDATE as ENDDATE0_0_, children0_.ISDECISIONABLE as ISDECISI8_0_0_, children0_.ISSELECTABLE as ISSELECT9_0_0_ from ORGANIZATIONALUNIT children0_ where children0_.PARENT=?
Children count :1
Hibernate: update ORGANIZATIONALUNIT set ORGANIZATIONALUNITCATALOG_ID=null where ORGANIZATIONALUNITCATALOG_ID=?
Hibernate: update ORGANIZATIONALUNIT set PARENT=null where PARENT=?
Hibernate: update ORGANIZATIONALUNIT set ORGANIZATIONALUNITCATALOG_ID=? where ID=?
Hibernate: update ORGANIZATIONALUNIT set PARENT=? where ID=?
Hibernate: update ORGANIZATIONALUNIT set PARENT=? where ID=?
Hibernate: update ORGANIZATIONALUNIT set ORGANIZATIONALUNITCATALOG_ID=null where ORGANIZATIONALUNITCATALOG_ID=?
Hibernate: update ORGANIZATIONALUNIT set PARENT=null where PARENT=?
Hibernate: update ORGANIZATIONALUNIT set ORGANIZATIONALUNITCATALOG_ID=? where ID=?
Hibernate: update ORGANIZATIONALUNIT set PARENT=? where ID=?
Hibernate: update ORGANIZATIONALUNIT set PARENT=? where ID=?



Thanks
Indrani


Top
 Profile  
 
 Post subject: Re: Hibernate issuing updates when collection is not modified
PostPosted: Mon Apr 25, 2011 5:02 pm 
Expert
Expert

Joined: Wed Mar 03, 2004 6:35 am
Posts: 1240
Location: Lund, Sweden
Quote:
return ImmutableSet.copyOf(children);


This is the problem. Hibernate requires that you return the original instance from the getter method. If not, Hibernate assumes that it has been modified. A possible solution is to make the getChildren() method private, and then provide an alternate method eg. getChildrenCopy() with public access using the code above.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 2 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.