Erreur Hibernate: un objet différent avec la même valeur d'identifiant était déjà associé à la session

91

J'ai essentiellement des objets dans cette configuration (le vrai modèle de données est un peu plus complexe):

  • A a une relation plusieurs-à-plusieurs avec B. (B a inverse="true")
  • B a une relation plusieurs-à-un avec C. (je me suis cascademis à "save-update")
  • C est une sorte de table de type / catégorie.

De plus, je devrais probablement mentionner que les clés primaires sont générées par la base de données lors de la sauvegarde.

Avec mes données, je rencontre parfois des problèmes où A a un ensemble d'objets B différents, et ces objets B font référence au même objet C.

Quand je l' appelle session.saveOrUpdate(myAObject), je reçois une erreur veille prolongée en disant: "a different object with the same identifier value was already associated with the session: C". Je sais que hibernate ne peut pas insérer / mettre à jour / supprimer le même objet deux fois dans la même session, mais y a-t-il un moyen de contourner ce problème? Cela ne semble pas être une situation si rare.

Au cours de mes recherches sur ce problème, j'ai vu des gens suggérer l'utilisation de session.merge(), mais lorsque je fais cela, tous les objets "en conflit" sont insérés dans la base de données en tant qu'objets vides avec toutes les valeurs définies sur null. Ce n'est clairement pas ce que nous voulons.

[Edit] Une autre chose que j'ai oublié de mentionner est que (pour des raisons architecturales indépendantes de ma volonté), chaque lecture ou écriture doit être effectuée dans une session distincte.

John
la source
Voyez si cette réponse vous aide ..
joaonlima

Réponses:

97

C'est probablement parce que les objets B ne font pas référence à la même instance d'objet Java C. Ils font référence à la même ligne dans la base de données (c'est-à-dire à la même clé primaire) mais ils en sont des copies différentes.

Donc, ce qui se passe, c'est que la session Hibernate, qui gère les entités, garderait une trace de quel objet Java correspond à la ligne avec la même clé primaire.

Une option serait de s'assurer que les entités des objets B qui font référence à la même ligne se réfèrent réellement à la même instance d'objet de C. Sinon, désactivez la cascade pour cette variable membre. De cette façon, lorsque B persiste, C ne l'est pas. Vous devrez cependant enregistrer C manuellement séparément. Si C est une table de type / catégorie, alors il est probablement logique d'être de cette façon.

jbx
la source
3
Merci jbx. Comme vous l'avez dit, il s'avère que les objets B font référence à plusieurs instances C en mémoire. Ce qui se passe essentiellement, c'est qu'une partie de mon programme lit en C, et l'attache à B. Une autre partie charge un autre B avec le même C à partir de la base de données. Les deux sont attachés à A, ce qui déclenche l'erreur lors de l'enregistrement. J'ai défini la <pre> cascade </pre> pour la relation B-> C sur "<pre> aucun </pre>", mais j'obtiens toujours la même erreur. Dans les relations plusieurs-à-un ou un-à-plusieurs, existe-t-il un moyen de dire à Hibernate uniquement de changer la clé étrangère et de ne pas s'inquiéter du reste?
John
1
La clé primaire de C a-t-elle une stratégie de génération d'ID? Comme un générateur de séquence ou quelque chose de similaire?
jbx
Oui, chacun a sa propre séquence dans la base de données. Comme vous l'avez mentionné, la cascade s'est avérée être le problème. Nous avons désactivé la mise en cascade pour les tables de types, et pour les autres, nous avons utilisé la cascade "merge", qui nous a permis d'appeler merge () sans créer toutes ces lignes nulles. J'ai marqué votre réponse en conséquence, merci!
John
14
J'ai utilisé merge () au lieu de saveOrUpdate () et BOOM! ça marche :)
Lahiru Ruhunage
27

Réglez simplement cascade sur MERGE, cela devrait faire l'affaire.

andy.codes
la source
13

Vous n'avez besoin de faire qu'une seule chose. Exécutez session_object.clear()puis enregistrez le nouvel objet. Cela effacera la session (comme il porte bien son nom) et supprimera l'objet dupliqué incriminé de votre session.

dsk
la source
9

Je suis d'accord avec @Hemant Kumar, merci beaucoup. Selon sa solution, j'ai résolu mon problème.

Par exemple:

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

}

Ce code fait toujours erreur dans mon application:, A different object with the same identifier value was already associated with the sessionplus tard, j'ai découvert que j'avais oublié d' augmenter automatiquement ma clé primaire!

Ma solution est d'ajouter ce code sur votre clé primaire:

@GeneratedValue(strategy = GenerationType.AUTO)
Glace bleue
la source
6

Cela signifie que vous essayez d'enregistrer plusieurs lignes dans votre table avec la référence au même objet.

vérifiez la propriété id de votre classe d'entité.

@Id
private Integer id;

à

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(unique = true, nullable = false)
private Integer id;
rex roy
la source
1
pas le cas avec question selon la description donnée
Sudip Bhandari
5

Transférez la tâche d'attribution de l'ID d'objet d'Hibernate à la base de données en utilisant:

<generator class="native"/>

Cela a résolu le problème pour moi.

user2845946
la source
3

Ajoutez l'annotation @GeneratedValue au bean que vous insérez.

Hemant kumar
la source
Merci! C'était exactement mon problème
DriLLFreAK100
3

Une façon de résoudre le problème ci-dessus sera de remplacer le fichier hashcode().
Videz également la session de mise en veille prolongée avant et après l'enregistrement.

getHibernateTemplate().flush();

La définition explicite de l'objet détaché sur est nullégalement utile.

Waqar
la source
2

Je viens de tomber sur ce message mais en code c #. Je ne sais pas si c'est pertinent (exactement le même message d'erreur cependant).

Je déboguais le code avec des points d'arrêt et étendais certaines collections via des membres privés alors que le débogueur était à un point d'arrêt. Après avoir réexécuté le code sans fouiller dans les structures, le message d'erreur disparaît. Il semble que le fait de rechercher dans des collections privées chargées paresseusement ait obligé NHibernate à charger des choses qui n'étaient pas censées être chargées à ce moment-là (car elles étaient dans des membres privés).

Le code lui-même est enveloppé dans une transaction assez compliquée qui peut mettre à jour un grand nombre d'enregistrements et de nombreuses dépendances dans le cadre de cette transaction (processus d'importation).

Espérons un indice pour toute autre personne qui rencontre le problème.

Ales Potocnik Hahonina
la source
2

Recherchez l'attribut «Cascade» dans Hibernate et supprimez-le. Lorsque vous définissez "Cascade" disponible, il appellera d'autres opérations (enregistrer, mettre à jour et supprimer) sur une autre entité qui a une relation avec les classes associées. Donc, la même valeur d'identité se produira. Cela a fonctionné avec moi.

Nguyen Vu Quang
la source
1

J'ai eu cette erreur quelques jours et j'ai trop vite résolu cette erreur.

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

Avant d'obtenir cette erreur, je n'avais pas mentionné le type de génération d'ID sur l'objet OrderDetil. quand sans générer l'ID de Orderdetails, il garde l'ID à 0 pour chaque objet OrderDetail. c'est ce que #jbx a expliqué. Oui, c'est la meilleure réponse. ceci un exemple comment cela se produit.

Buddhi
la source
1

Essayez de placer le code de votre requête avant. Cela résout mon problème. par exemple, changez ceci:

query1 
query2 - get the error 
update

pour ça:

query2
query1
update
Juldeh
la source
0

vous ne définissez peut-être pas l'identificateur de l'objet avant d'appeler la requête de mise à jour.

Fawad Khaliq
la source
3
S'il ne l'était pas, il n'aurait pas ce problème. Le problème est qu'il a deux objets avec le même identifiant.
aalku
0

J'ai rencontré le problème car la génération de la clé primaire est incorrecte, lorsque j'insère une ligne comme celle-ci:

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

Je change la classe du générateur d'identifiant en identité

<id name="id" type="int">
    <column name="id" />
    <generator class="identity"  />
 </id>
张云 风
la source
0

Dans mon cas, seul flush () ne fonctionnait pas. J'ai dû utiliser un clear () après flush ().

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

si vous utilisez EntityRepository, utilisez saveAndFlush au lieu de save

Jayen Chondigara
la source
0

Si laissé un onglet d'expressions dans mon IDE ouvert qui faisait un appel get hibernate sur l'objet à l'origine de cette exception. J'essayais de supprimer ce même objet. J'ai également eu un point d'arrêt sur l'appel de suppression qui semble être nécessaire pour que cette erreur se produise. Le simple fait de créer un autre onglet d'expressions en tant qu'onglet avant ou de modifier le paramètre pour que l'ide ne s'arrête pas aux points d'arrêt a résolu ce problème.

Aaron Byrnes
la source
0

Assurez-vous que votre entité a le même type de génération avec toutes les entités mappées

Ex: UserRole

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;

}

Module:

public class Modules implements Serializable {

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

private String longName;

private String shortName;

}

Entité principale avec mappage

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

}

Sandeep Patel
la source
0

En plus de toutes les réponses précédentes, une solution possible à ce problème dans un projet à grande échelle, si vous utilisez un objet de valeur pour vos classes ne définissez pas l'attribut id dans la classe VO Transformer.

George michael
la source
0

validez simplement la transaction en cours.

currentSession.getTransaction().commit();

maintenant vous pouvez commencer une autre transaction et faire n'importe quoi sur l'entité

Mahdi
la source