Errore di ibernazione: un oggetto diverso con lo stesso valore identificativo era già associato alla sessione


91

In sostanza ho alcuni oggetti in questa configurazione (il modello di dati reale è un po 'più complesso):

  • A ha una relazione molti-a-molti con B. (B ha inverse="true")
  • B ha una relazione molti-a-uno con C. (ho cascadedeciso di "save-update")
  • C è una sorta di tabella tipo / categoria.

Inoltre, dovrei probabilmente menzionare che le chiavi primarie vengono generate dal database al momento del salvataggio.

Con i miei dati, a volte mi imbatto in problemi in cui A ha un insieme di oggetti B diversi e questi oggetti B si riferiscono allo stesso oggetto C.

Quando chiamo session.saveOrUpdate(myAObject), ottengo un errore hibernate dicendo: "a different object with the same identifier value was already associated with the session: C". So che l'ibernazione non può inserire / aggiornare / eliminare lo stesso oggetto due volte nella stessa sessione, ma c'è un modo per aggirare questo? Questa non sembra essere una situazione così insolita.

Durante la mia ricerca su questo problema, ho visto persone suggerire l'uso di session.merge(), ma quando lo faccio, qualsiasi oggetto "in conflitto" viene inserito nel database come oggetti vuoti con tutti i valori impostati su null. Chiaramente non è quello che vogliamo.

[Modifica] Un'altra cosa che ho dimenticato di menzionare è che (per ragioni architettoniche al di fuori del mio controllo), ogni lettura o scrittura deve essere eseguita in una sessione separata.


Vedi se questa risposta ti aiuta ..
joaonlima

Risposte:


97

Molto probabilmente è perché gli oggetti B non si riferiscono alla stessa istanza dell'oggetto Java C. Si riferiscono alla stessa riga nel database (cioè la stessa chiave primaria) ma ne sono copie diverse.

Quello che sta succedendo è che la sessione Hibernate, che gestisce le entità, terrà traccia di quale oggetto Java corrisponde alla riga con la stessa chiave primaria.

Un'opzione potrebbe essere quella di assicurarsi che le entità degli oggetti B che fanno riferimento alla stessa riga si riferiscano effettivamente alla stessa istanza di oggetto di C. In alternativa, disattivare la cascata per quella variabile membro. In questo modo quando B è persistente C non lo è. Tuttavia, dovrai salvare C manualmente separatamente. Se C è una tabella di tipo / categoria, probabilmente ha senso che sia così.


3
Grazie jbx. Come hai detto, risulta che gli oggetti B si riferiscono a più istanze C in memoria. In sostanza, ciò che sta accadendo è che una parte del mio programma sta leggendo in C e allegandola a B. Un'altra parte sta caricando una B diversa con la stessa C dal database. Entrambi vengono collegati ad A che attiva l'errore al salvataggio. Ho impostato la <pre> cascata </pre> per la relazione B-> C su "<pre> nessuno </pre>", ma ricevo ancora lo stesso errore. Nelle relazioni molti-a-uno o uno-a-molti, c'è un modo per dire a Hibernate solo di cambiare la chiave esterna e di non preoccuparsi del resto?
Giovanni

1
La chiave primaria di C ha una strategia di generazione dell'ID? Come un generatore di sequenze o qualcosa di simile?
jbx

Sì, ognuno ha la propria sequenza nel database. Come hai detto, il problema si è rivelato essere a cascata. Abbiamo disattivato la cascata per le tabelle di tipo, e per le altre abbiamo usato "merge" cascade, che ci ha permesso di chiamare merge () senza creare tutte quelle righe null. Ho contrassegnato la tua risposta di conseguenza, grazie!
John

14
Ho usato merge () invece di saveOrUpdate () e BOOM! funziona :)
Lahiru Ruhunage


13

Hai solo bisogno di fare una cosa. Esegui session_object.clear()e quindi salva il nuovo oggetto. Questo cancellerà la sessione (come giustamente chiamato) e rimuoverà l'oggetto duplicato offensivo dalla tua sessione.


9

Sono d'accordo con @Hemant Kumar, grazie mille. Secondo la sua soluzione, ho risolto il mio problema.

Per esempio:

@Test
public void testSavePerson() {
    try (Session session = sessionFactory.openSession()) {
        Transaction tx = session.beginTransaction();
        Person person1 = new Person();
        Person person2 = new Person();
        person1.setName("222");
        person2.setName("111");
        session.save(person1);
        session.save(person2);
        tx.commit();
    }
}

Person.java

public class Person {
    private int id;
    private String name;

    @Id
    @Column(name = "id")
    public int getId() {
        return id;
    }

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

    @Basic
    @Column(name = "name")
    public String getName() {
        return name;
    }

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

}

Questo codice fa sempre degli errori nella mia applicazione: in A different object with the same identifier value was already associated with the sessionseguito ho scoperto di aver dimenticato di aumentare automaticamente la mia chiave primaria!

La mia soluzione è aggiungere questo codice sulla tua chiave primaria:

@GeneratedValue(strategy = GenerationType.AUTO)

6

Ciò significa che stai tentando di salvare più righe nella tabella con il riferimento allo stesso oggetto.

controlla la proprietà id della tua classe di entità.

@Id
private Integer id;

per

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(unique = true, nullable = false)
private Integer id;

1
non è il caso della domanda come da descrizione fornita
Sudip Bhandari

5

Trasferire l'attività di assegnazione dell'ID oggetto da Hibernate al database utilizzando:

<generator class="native"/>

Questo ha risolto il problema per me.



3

Un modo per risolvere il problema di cui sopra sarà ignorare il file hashcode().
Svuota anche la sessione di ibernazione prima e dopo il salvataggio.

getHibernateTemplate().flush();

Anche l'impostazione esplicita dell'oggetto scollegato su nullaiuta.


2

Mi sono appena imbattuto in questo messaggio ma in codice c #. Non sono sicuro che sia rilevante (esattamente lo stesso messaggio di errore però).

Stavo eseguendo il debug del codice con punti di interruzione e ho espanso alcune raccolte tramite membri privati ​​mentre il debugger era a un punto di interruzione. Dopo aver rieseguito il codice senza scavare nelle strutture, il messaggio di errore è scomparso. Sembra che l'atto di esaminare raccolte private caricate in modo pigro abbia fatto caricare a NHibernate cose che non avrebbero dovuto essere caricate in quel momento (perché erano in membri privati).

Il codice stesso è racchiuso in una transazione abbastanza complicata che può aggiornare un gran numero di record e molte dipendenze come parte di quella transazione (processo di importazione).

Si spera che sia un indizio per chiunque altro incontri il problema.


2

Trova l'attributo "Cascade" in Hibernate ed eliminalo. Quando imposti "Cascade" disponibile, chiamerà altre operazioni (salva, aggiorna ed elimina) su un'altra entità che ha una relazione con le classi correlate. Quindi si verificherà lo stesso valore di identità. Ha funzionato con me.


1

Ho riscontrato questo errore per alcuni giorni e ho accelerato troppo tempo per correggere questo errore.

 public boolean save(OrderHeader header) {
    Session session = sessionFactory.openSession();


    Transaction transaction = session.beginTransaction();

    try {
        session.save(header);

        for (OrderDetail detail : header.getDetails()) {
            session.save(detail);
        }

        transaction.commit();
        session.close();

        return true;
    } catch (HibernateException exception) {

        exception.printStackTrace();
        transaction.rollback();
        return false;
    }
}

Prima di ottenere questo errore, non avevo menzionato il tipo di generazione dell'ID sull'oggetto OrderDetil. quando senza generare l'ID Orderdetails, mantiene Id come 0 per ogni oggetto OrderDetail. questo è ciò che ha spiegato #jbx. Sì, è la migliore risposta. questo un esempio di come accade.


1

Prova a inserire prima il codice della tua query. Questo risolve il mio problema. ad es. cambia questo:

query1 
query2 - get the error 
update

a questa:

query2
query1
update

0

potresti non impostare l'identificatore dell'oggetto prima di chiamare la query di aggiornamento.


3
Se non lo fosse, non avrebbe questo problema. Il problema è che ha due oggetti con lo stesso identificatore.
aalku

0

Ho riscontrato il problema perché la generazione della chiave primaria è sbagliata, quando inserisco una riga come questa:

public void addTerminal(String typeOfDevice,Map<Byte,Integer> map) {
        // TODO Auto-generated method stub
        try {
            Set<Byte> keySet = map.keySet();
            for (Byte byte1 : keySet) {
                Device device=new Device();
                device.setNumDevice(DeviceCount.map.get(byte1));
                device.setTimestamp(System.currentTimeMillis());
                device.setTypeDevice(byte1);
                this.getHibernateTemplate().save(device);
            }
            System.out.println("hah");
        }catch (Exception e) {
            // TODO: handle exception
            logger.warn("wrong");
            logger.warn(e.getStackTrace()+e.getMessage());
        }
}

Cambio la classe id generator in identity

<id name="id" type="int">
    <column name="id" />
    <generator class="identity"  />
 </id>

0

Nel mio caso solo flush () non ha funzionato. Ho dovuto usare un clear () dopo flush ().

public Object merge(final Object detachedInstance)
    {
        this.getHibernateTemplate().flush();
        this.getHibernateTemplate().clear();
        try
        {
            this.getHibernateTemplate().evict(detachedInstance);
        }
}


0

Se ho lasciato aperta una scheda delle espressioni nel mio IDE che stava effettuando una chiamata di ibernazione sull'oggetto che causava questa eccezione. Stavo cercando di eliminare questo stesso oggetto. Inoltre ho avuto un punto di interruzione sulla chiamata di cancellazione che sembra essere necessario per far accadere questo errore. Il problema è stato risolto semplicemente facendo in modo che un'altra scheda delle espressioni fosse la scheda principale o modificando l'impostazione in modo che l'ide non si fermasse sui punti di interruzione.


0

Assicurati che la tua entità abbia lo stesso tipo di generazione con tutte le entità mappate

Es: ruolo utente

public class UserRole extends AbstractDomain {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

private String longName;

private String shortName;

@Enumerated(EnumType.STRING)
private CommonStatus status;

private String roleCode;

private Long level;

@Column(columnDefinition = "integer default 0")
private Integer subRoleCount;

private String modification;

@ManyToOne(fetch = FetchType.LAZY)
private TypeOfUsers licenseType;

}

Modulo:

public class Modules implements Serializable {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

private String longName;

private String shortName;

}

Entità principale con mappatura

public class RoleModules implements Serializable{

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
private UserRole role;

@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
private Modules modules;

@Type(type = "yes_no")
private boolean isPrimaryModule;

public boolean getIsPrimaryModule() {
    return isPrimaryModule;
}

}


0

Oltre a tutte le risposte precedenti, una possibile soluzione a questo problema in un progetto su larga scala, se si utilizza un oggetto valore per le classi non si imposta l'attributo id nella classe Transformer VO.


0

basta eseguire il commit della transazione corrente.

currentSession.getTransaction().commit();

ora puoi iniziare un'altra transazione e fare qualsiasi cosa sull'entità

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.