开发者

Hibernate issuing updates when collection is not modified

开发者 https://www.devze.com 2023-03-01 02:36 出处:网络
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.

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> 
0

精彩评论

暂无评论...
验证码 换一张
取 消