Struts / hibernate NonUniqueObjectException:具有相同标识符值的不同对象已与会话关联

Posted

技术标签:

【中文标题】Struts / hibernate NonUniqueObjectException:具有相同标识符值的不同对象已与会话关联【英文标题】:Struts / hibernate NonUniqueObjectException: different object with the same identifier value was already associated with the session 【发布时间】:2018-03-26 01:43:56 【问题描述】:

我有以下问题:

org.hibernate.NonUniqueObjectException: 具有相同标识符值的不同对象已与会话关联。

奇怪的是,它并没有系统地发生。

实体从数据库加载到 Struts 并在 HTTP 会话中设置。 修改后将其发送到业务服务进行保存。 (sessionFactory.update, sessionFactory.save)

它应该总是有休眠问题,但org.hibernate.NonUniqueObjectException 只出现几次(不知道为什么)

有人解释一下吗? 我考虑在我的业务服务中从休眠会话重新加载对象,并从 Struts HTTP 会话的实体对象复制数据。

@Override
    public boolean equals(Object autre) 
        if (this == autre) 
            return true;
        
        if ((autre == null) || (autre.getClass() != this.getClass())) 
            return false;
        
        MouvementFinancier entite = (MouvementFinancier) autre;
        if (pk == null || entite.pk == null) 
            return false;
        
        return pk.equals(entite.pk);
    

    @Override
    public int hashCode() 
        if (pk == null) 
            return super.hashCode();
        
        return pk.hashCode();
    

休眠映射

 <class name="com.XXX.MouvementFinancier" table="MOUVEMENT_FINANCIER"
            discriminator-value="0" abstract="true">

            <id name="pk" type="integer" column="PK_MOUVEMENT_FINANCIER" unsaved-value="null">
                <generator class="com.XXX.TableGenerator">
                    <param name="segment_value">MOUVEMENT_FINANCIER</param>
                </generator>
            </id>

            <discriminator column="CLASSE" type="integer" />
            <timestamp column="DATE_VERSION" name="version" unsaved-value="null" />

            <property name="commentaire" column="COMMENTAIRE" />
            ...

            <set name="actes" cascade="all,delete-orphan" fetch="select" sort="natural">
                <key column="PK_MOUVEMENT_FINANCIER" not-null="true" />
                <one-to-many class="com.p****.HistoriqueMouvement" />
            </set>

            <subclass name="com.XXX.Encaissement" discriminator-value="2">
                <property name="purpose" column="NATURE_RECUPERATION" />
                ...

                <many-to-one name="emetteur" column="PK_PERSONNE_EMETTEUR"
                    class="com.XXX.Personne" cascade="none" fetch="join" lazy="false"/>

                <many-to-one name="acteurEmetteur" column="PK_ACTEUR_EMETTEUR"
                    class="com.XXX.Acteur" cascade="none" fetch="join" lazy="false"/>
            </subclass>

            <subclass name="com.XXX.Reglement" discriminator-value="1">
                <property name="dateAutorisation" type="timestamp" column="DATE_AUTORISATION" />
                <property name="franchise" column="FRANCHISE" />
                ...

                <many-to-one name="beneficiaire" column="PK_PERSONNE_BENEFICIAIRE"
                    class="com.XXX.Personne" cascade="none" fetch="join" lazy="false"/>

                <many-to-one name="acteurBeneficiaire" column="PK_ACTEUR_BENEFICIAIRE"
                    class="com.XXX.Acteur" cascade="none" fetch="join" lazy="false"/>

                <many-to-one name="adresseCourrierReglementBeneficiaire" column="PK_ADD_COUR_REG_BENEF"
                    class="com.XXX.AdresseCourrierReglement" cascade="none" fetch="join" lazy="false"/>

                <many-to-one name="destinataire" column="PK_PERSONNE_DESTINATAIRE"
                    class="com.XXX.Personne" cascade="none" fetch="join" lazy="false"/>

                <many-to-one name="acteurDestinataire" column="PK_ACTEUR_DESTINATAIRE"
                    class="com.XXX.Acteur" cascade="none" fetch="join" lazy="false"/>

                <many-to-one name="adresseCourrierReglementDestinataire" column="PK_ADD_COUR_REG_DEST"
                    class="com.XXX.AdresseCourrierReglement" cascade="none" fetch="join" lazy="false"/>

                <many-to-one name="rib" column="PK_RIB" class="com.XXX.assureur.RIB"
                    cascade="all" fetch="select" />

                <set name="ventilationDepenses" cascade="all,delete-orphan" fetch="select">
                    <key column="PK_MOUVEMENT_FINANCIER" not-null="true" />
                    <one-to-many class="com.XXX.Depense"/>
                </set>

            </subclass>

        </class>

        <class name="com.XXXX.Depense" table="VENTILATION_DEPENSES">
            <id name="pk" type="integer" column="PK_VENTILATION_DEPENSE" unsaved-value="null">
                <generator class="com.XXX.TableGenerator">
                    <param name="segment_value">VENTILATION_DEPENSES</param>
                </generator>
            </id>

            <property name="nature" column="CODE_NATURE_DEPENSE" />
            <property name="montant" column="MONTANT_DEPENSE" />
            ...
        </class>

【问题讨论】:

检查hashCode/equals方法是否正确实现。 Struts 没有错误地使用 HTTP 会话,它对用户隐藏了它。 equals & hascode 方法已经设置好了。我添加代码+休眠映射 【参考方案1】:

问题已通过在 DAO 中使用 Hiberante 合并解决 但我仍然想知道为什么在大多数情况下没有使用合并命令或复制从休眠会话加载的实体中将“分离”实体发送​​回服务层没有问题。

// TODO : save (twice) replaced by 1 merge to avoid issue on detached object
// if we have any more pb on this, redo all : load payment from session and copy data in from dto.
session.merge(reglement);

完整代码

    @Override
        public void update...(...) 
            if (logger.isDebugEnabled()) 
                logger.debug("Updating a settlement");
            
            Session session = sf.getCurrentSession();
            final PartieFinanciere partieReglement = (PartieFinanciere) session.get(PartieFinanciere.class, pkPartieFinanciere);
            if (partieReglement == null) 
                throw new ExceptionPkEntiteInconnu(PartieFinanciere.class, pkPartieFinanciere);
            

            // History : last movement amount to remove it on total amount
            HistoriqueMouvement movementHistoryLast = reglement.getActes().stream().sorted((h1, h2) -> h2.compareTo(h1)).findFirst().orElse(null);
            Double mouvementLastAmount = movementHistoryLast != null ? movementHistoryLast.getMontant() : 0;

            // History : add movement modification in history
            HistoriqueMouvement histo = new HistoriqueMouvement();
            histo.setActe(...);
            histo.setDate(...);
            histo.setMontant(...);
            .....
            reglement.getActes().add(histo);
            partieReglement.getMouvements().add(reglement);

            // Recalculate total amount : remove previous movement amount, set movement modified amount
            Double amountProvision = ofNullable(partieReglement.getTotalMouvements()).orElse(0.0).doubleValue() - mouvementLastAmount + reglement.getMontant();
            partieReglement.setTotalMouvements(amountProvision);

            if (logger.isDebugEnabled()) 
                logger.debug("Updating total mouvement and suspens.");
            

            mettreAJourTotalMouvementsEtSuspens(session, partieReglement);
            ajouterActePartieFinanciere(gestionnaire, partieReglement, getHistoriquePartieActe(null, reglement.getType(), false), reglement.getMontant());

// TODO : save (twice) replaced by 1 merge to avoid issue on detached object
// if we have any more pb on this, redo all : load payment from session and copy data in from dto.
session.merge(reglement);
 

【讨论】:

以上是关于Struts / hibernate NonUniqueObjectException:具有相同标识符值的不同对象已与会话关联的主要内容,如果未能解决你的问题,请参考以下文章

Struts+Hibernate+Spring面试题合集及答案

struts2集成Spring,Hibernate的问题!!

Struts + Hibernate:@SessionTarget 不工作

spring+struts+hibernate做的项目在控制台不输出SQL语句

Struts2 + Spring + Hibernate 分页

提交表单时Struts 2 Hibernate空指针异常