0
votes

I have the following issue :

org.hibernate.NonUniqueObjectException: A different object with the same identifier value was already associated with the session.

Strangely it does not occurs systematically.

Entity is loaded from database to Struts and set in HTTP Session. Once modified it's send to business service for saving. (sessionFactory.update, sessionFactory.save)

It should always have hibernate issue but the org.hibernate.NonUniqueObjectException occurs only some times (Don't know why)

Does any one have an explanation ? I think about reload the object from hibernate session in my business service and copy data from entity object from Struts HTTP Session.

@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();
    }

Hibernate mapping

 <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>
1
Check if hashCode/equals methods are implemented correctly. - lexicore
Struts doesn't use HTTP session incorrectly and it hided it from the user. - Roman C
equals & hascode methods are already set. I add the code + hibernate mapping - Anthony

1 Answers

1
votes

Issue has been solved by using Hiberante merge in DAO But I still wonder why in most case there was no issue to send back a "detached" entity to service layer without using merge command or copy in an entity loaded from hibernate session.

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

Complete code

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