Hibernate issuing updates when collection is not modified
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.
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
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 file
<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.java
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;
priv开发者_如何学JAVAate 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 file
<?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
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 Log
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
Perhaps you can configure Hibernate to use field access for your collection:
<set name="children" access = "field" ...>
...
</set>
精彩评论