Comment réparer la mise en veille prolongée de l'objet «fait référence à une instance transitoire non enregistrée - enregistrez l'instance transitoire avant de vider»


610

Je reçois l'erreur suivante lorsque j'enregistre l'objet à l'aide d'Hibernate

object references an unsaved transient instance - save the transient instance before flushing

1
Dans le contexte voulu, cette erreur? est-ce avec ou sans variable transitoire?
vijay

Réponses:


803

Vous devez inclure cascade="all"(si vous utilisez xml) ou cascade=CascadeType.ALL(si vous utilisez des annotations) sur le mappage de votre collection.

Cela se produit car vous avez une collection dans votre entité et cette collection a un ou plusieurs éléments qui ne sont pas présents dans la base de données. En spécifiant les options ci-dessus, vous dites à hibernate de les enregistrer dans la base de données lors de l'enregistrement de leur parent.


7
N'est-ce pas implicite? Ne voudriez-vous pas toujours qu'Hibernate les enregistre?
Marcus Leon

29
@Marcus - non, ce n'est pas le cas. Vous voudrez peut-être les gérer manuellement.
Bozho

5
Bozho a raison. J'ai rencontré des circonstances dans lesquelles j'avais des collections que je voulais gérer manuellement en raison de leur taille ou en raison de règles métier qui ne permettent pas à tous les objets d'une collection d'être enregistrés en même temps.
Alex Marshall

26
Cela ne se produit pas seulement pour les collections, mais aussi pour les mappings simples un à un
Sebastien Lorber

12
Ne serait-il pas préférable de commencer avec CascadeType.PERSIST et d'utiliser persist pour enregistrer?
Sergii Shevchyk

249

Je crois que cela pourrait être juste une réponse répétée, mais juste pour clarifier, j'ai obtenu ceci sur une @OneToOnecartographie ainsi que sur un @OneToMany. Dans les deux cas, c'était le fait que l' Childobjet que j'ajoutais Parentn'était pas encore enregistré dans la base de données. Ainsi, lorsque j'ajoutais le Childà Parent, puis enregistrais le Parent, Hibernate lancerait le "object references an unsaved transient instance - save the transient instance before flushing"message lors de l'enregistrement du parent.

Ajout dans le cascade = {CascadeType.ALL}sur la Parent'sréférence au Childrésoudre le problème dans les deux cas. Cela a sauvé le Childet le Parent.

Désolé pour toutes les réponses répétées, je voulais juste clarifier davantage pour les gens.

@OneToOne(cascade = {CascadeType.ALL})
@JoinColumn(name = "performancelog_id")
public PerformanceLog getPerformanceLog() {
    return performanceLog;
}

7
Que se passe-t-il si je ne souhaite pas enregistrer en cascade les économies sur une relation @OneToOne? Lors de la création des deux objets pour la première fois, comment puis-je enregistrer l'un ou l'autre dans la base de données sans déclencher l'exception?
2016

4
Et si je veux sauver l'enfant pour certains cas et pas pour d'autres?
Omaruchan

@xtian: Eh bien, vous devez prendre soin du bon ordre d'enregistrement dans la base de données en conservant les objets avec un EntityManager. En base, vous dites simplement em.persist (object1); em.persist (object2); etc.
kaba713

J'ai rencontré ce problème spécifiquement lorsque j'ai utilisé @Inheritance, dans ce cas TABLE_PER_CLASS, je faisais référence à une sous-classe. CascadeType.ALL l'a corrigé.
Jim ReesPotter

Ou vous avez créé votre objet entité avec new MyEntity(sans le synchroniser avec la base de données - vidage), au lieu d'obtenir son instance synchronisée de la base de données. Faire des requêtes Hibernate en utilisant cette instance vous informe que ce que vous attendez d'être dans la base de données diffère de ce que vous avez dans la mémoire de votre application. Dans ce cas, synchronisez / obtenez simplement votre entité à partir de la base de données et utilisez-la. Aucun CascadeType.ALL n'est alors nécessaire.
Zon

67

Cela se produit lors de l'enregistrement d'un objet lorsque Hibernate pense qu'il doit enregistrer un objet associé à celui que vous enregistrez.

J'ai eu ce problème et je ne voulais pas enregistrer les modifications apportées à l'objet référencé, je voulais donc que le type de cascade soit AUCUN.

L'astuce consiste à s'assurer que l'ID et la VERSION dans l'objet référencé sont définis de sorte que Hibernate ne pense pas que l'objet référencé est un nouvel objet qui doit être enregistré. Cela a fonctionné pour moi.

Parcourez toutes les relations de la classe que vous enregistrez pour déterminer les objets associés (et les objets associés des objets associés) et assurez-vous que l'ID et la VERSION sont définis dans tous les objets de l'arborescence d'objets.


4
Ce commentaire m'a mis sur la bonne voie. J'assignais une nouvelle instance du parent dans une propriété de son enfant. NH a donc pensé qu'il s'agissait de cas différents.
elvin

2
Oui. Cela se produit si, par exemple, l'id de l'objet associé n'est pas inclus (par exemple, il a été ignoré par @JsonIgnore). Hibernate n'a aucun moyen d'identifier l'entité associée, il souhaite donc la sauvegarder.
Rori Stumpf

36

introduction

Comme je l'ai expliqué dans cet article lors de l'utilisation de JPA et Hibernate, une entité peut se trouver dans l'un des 4 états suivants:

  • Nouveau - Un objet nouvellement créé qui n'a jamais été associé à une session de mise en veille prolongée (aka Contexte de persistance) et qui n'est mappé à aucune ligne de table de base de données est considéré comme étant à l'état Nouveau ou Transitoire.

    Pour devenir persistant, nous devons soit appeler explicitement la persistméthode, soit utiliser le mécanisme de persistance transitive.

  • Persistant - Une entité persistante a été associée à une ligne de table de base de données et elle est gérée par le contexte de persistance en cours d'exécution.

    Toute modification apportée à une telle entité va être détectée et propagée à la base de données (pendant le vidage de session).

  • Détaché - Une fois que le contexte de persistance en cours d'exécution est fermé, toutes les entités précédemment gérées se détachent. Les modifications successives ne seront plus suivies et aucune synchronisation automatique de la base de données ne se produira.

  • Supprimé - Bien que JPA exige que seules les entités gérées soient supprimées, Hibernate peut également supprimer les entités détachées (mais uniquement via un removeappel de méthode).

Transitions d'état d'entité

Pour déplacer une entité d'un état à l'autre, vous pouvez utiliser les méthodes persist, removeou merge.

États de l'entité JPA

Résoudre le problème

Le problème que vous décrivez dans votre question:

object references an unsaved transient instance - save the transient instance before flushing

est causée par l'association d'une entité à l'état Nouveau à une entité à l'état Géré .

Cela peut se produire lorsque vous associez une entité enfant à une collection un à plusieurs dans l'entité parent et que la collection ne cascade les transitions d'état d'entité.

Ainsi, comme je l'ai expliqué dans cet article , vous pouvez résoudre ce problème en ajoutant une cascade à l'association d'entité qui a déclenché cet échec, comme suit:

L' @OneToOneassociation

@OneToOne(
    mappedBy = "post",
    orphanRemoval = true,
    cascade = CascadeType.ALL)
private PostDetails details;

Notez la CascadeType.ALLvaleur que nous avons ajoutée pour l' cascadeattribut.

L' @OneToManyassociation

@OneToMany(
    mappedBy = "post", 
    orphanRemoval = true,
    cascade = CascadeType.ALL)
private List<Comment> comments = new ArrayList<>();

Encore une fois, le CascadeType.ALLconvient aux associations bidirectionnelles@OneToMany .

Maintenant, pour que la cascade fonctionne correctement dans un sens bidirectionnel, vous devez également vous assurer que les associations parent et enfant sont synchronisées.

Consultez cet article pour plus de détails sur la meilleure façon d'atteindre cet objectif.

L' @ManyToManyassociation

@ManyToMany(
    mappedBy = "authors",
    cascade = {
        CascadeType.PERSIST, 
        CascadeType.MERGE
    }
)
private List<Book> books = new ArrayList<>();

Dans une @ManyToManyassociation, vous ne pouvez pas utiliser CascadeType.ALLouorphanRemoval car cela propagera la transition d'état de suppression de l'entité d'un parent à une autre entité parent.

Par conséquent, pour les @ManyToManyassociations, vous mettez généralement en cascade les opérations CascadeType.PERSISTor CascadeType.MERGE. Vous pouvez également l'étendre à DETACHou REFRESH.

Pour plus de détails sur la meilleure façon de mapper une @ManyToManyassociation, consultez également cet article .


Explication complète et cohérente! Bon travail!
Froid

Vous pouvez trouver des centaines de ces explications détaillées dans mon tutoriel Hibernate .
Vlad Mihalcea

30

Ou, si vous voulez utiliser un minimum de "pouvoirs" (par exemple si vous ne voulez pas une suppression en cascade) pour obtenir ce que vous voulez, utilisez

import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;

...

@Cascade({CascadeType.SAVE_UPDATE})
private Set<Child> children;

11
Ce devrait être la réponse acceptée. CascadeType.ALL est trop large
lilalinux

5
Depuis Hibernate 5.2.8, il semble qu'il n'y ait aucun moyen d'obtenir le même effet avec les annotations JPA. Par exemple, @ManyToMany(cascade={PERSIST, MERGE, REFRESH, DETACH})(tout sauf REMOVE) ne met pas en cascade les mises à jour comme le fait Hibernate CascadeType.SAVE_UPDATE.
jcsahnwaldt dit GoFundMonica

25

Dans mon cas, cela a été causé par le fait de ne pas être CascadeTypedu @ManyToOnecôté de la relation bidirectionnelle. Pour être plus précis, je l'avais CascadeType.ALLsur le @OneToManycôté et je ne l'avais pas @ManyToOne. Ajout CascadeType.ALLpour @ManyToOnerésoudre le problème. Côté un à plusieurs :

@OneToMany(cascade = CascadeType.ALL, mappedBy="globalConfig", orphanRemoval = true)
private Set<GlobalConfigScope>gcScopeSet;

Côté plusieurs-à-un (a causé le problème)

@ManyToOne
@JoinColumn(name="global_config_id")
private GlobalConfig globalConfig;

Plusieurs à un (corrigé en ajoutant CascadeType.PERSIST)

@ManyToOne(cascade = CascadeType.PERSIST)
@JoinColumn(name="global_config_id")
private GlobalConfig globalConfig;

Si je le fais de cette façon et que j'enregistre l'entité parent, ce qui se passe, c'est qu'il y a deux insertions dans ma table parent qui sont deux lignes. Je pense que c'est parce que nous avons une cascade sur les entités parent et enfant?
theprogrammer

18

Cela s'est produit pour moi lors de la persistance d'une entité dans laquelle l'enregistrement existant dans la base de données avait une valeur NULL pour le champ annoté avec @Version (pour un verrouillage optimiste). La mise à jour de la valeur NULL à 0 dans la base de données a corrigé cela.


Cela devrait être une nouvelle question et elle devrait être ajoutée en tant que bogue, au moins l'exception trompeuse. Cela s'est avéré être la cause de mon problème.
BML

Cela résout mon problème
Jad Chahine

11

Ce n'est pas la seule raison de l'erreur. Je l'ai rencontré tout à l'heure pour une erreur de frappe dans mon codage, qui, je crois, a défini une valeur d'une entité qui était déjà enregistrée.

X x2 = new X();
x.setXid(memberid); // Error happened here - x was a previous global entity I created earlier
Y.setX(x2);

J'ai repéré l'erreur en trouvant exactement quelle variable a causé l'erreur (dans ce cas String xid). J'ai utilisé un catchautour du bloc de code entier qui a sauvé l'entité et imprimé les traces.

{
   code block that performed the operation
} catch (Exception e) {
   e.printStackTrace(); // put a break-point here and inspect the 'e'
   return ERROR;
}

1
Problème similaire au mien. Après tout, lorsque j'ai rechargé l'entité localement, défini la propriété, puis enregistré, cela a bien fonctionné.
CsBalazsHungary

8

N'utilisez pas Cascade.Allavant d'avoir vraiment besoin. Roleet Permissionont une manyToManyrelation bidirectionnelle . Ensuite, le code suivant fonctionnerait bien

    Permission p = new Permission();
    p.setName("help");
    Permission p2 = new Permission();
    p2.setName("self_info");
    p = (Permission)crudRepository.save(p); // returned p has id filled in.
    p2 = (Permission)crudRepository.save(p2); // so does p2.
    Role role = new Role();
    role.setAvailable(true);
    role.setDescription("a test role");
    role.setRole("admin");
    List<Permission> pList = new ArrayList<Permission>();
    pList.add(p);
    pList.add(p2);
    role.setPermissions(pList);
    crudRepository.save(role);

tandis que si l'objet est juste un "nouveau", alors il lancerait la même erreur.


1
est 100% correct, le Cascade.Tout est la solution paresseuse et ne doit être appliqué que lorsque cela est nécessaire. tout d'abord, si l'entité existe déjà, vérifiez si elle est chargée dans le gestionnaire d'entités actuel, sinon chargez-la.
Renato Mendes

7

Si votre collection est nullable, essayez simplement: object.SetYouColection(null);


C'était totalement mon problème. Je n'aurais jamais pensé que je devais le régler manuellement sur null.
Deadron

C'était aussi mon problème. Je n'utilisais pas de collection, donc je n'ai pas essayé au début, mais j'ai défini mon objet sur null et maintenant cela fonctionne.
Fodder

5

Pour ajouter mes 2 cents, j'ai eu ce même problème lorsque j'envoyais accidentellement nullcomme ID. Le code ci-dessous décrit mon scénario (et OP n'a mentionné aucun scénario spécifique) .

Employee emp = new Employee();
emp.setDept(new Dept(deptId)); // --> when deptId PKID is null, same error will be thrown
// calls to other setters...
em.persist(emp);

Ici, je mets l'ID de service existant sur une nouvelle instance d'employé sans obtenir d'abord l'entité de service, car je ne veux pas qu'une autre requête de sélection se déclenche.

Dans certains scénarios, deptIdPKID arrive commenull de la méthode d'appel et la même erreur.

Alors, surveillez les nullvaleurs pour PK ID


Et si c'était nul?
vipin cp

J'ai un problème similaire. J'obtiens l'exception lorsque mon deptID est 0. Toute autre valeur supérieure à 0 fonctionne. C'est drôle que j'ai un département avec id = 0.
Gustavo

5

Ce problème m'est arrivé lorsque j'ai créé une nouvelle entité et une entité associée dans une méthode marquée comme @Transactional, puis effectué une requête avant d'enregistrer. Ex

@Transactional
public someService() {
    Entity someEntity = new Entity();
    AssocaiatedEntity associatedEntity = new AssocaitedEntity();
    someEntity.setAssociatedEntity(associatedEntity);
    associatedEntity.setEntity(someEntity);

    // Performing any query was causing hibernate to attempt to persist the new entity. It would then throw an exception
    someDao.getSomething();

    entityDao.create(someEntity);
}

Pour corriger, j'ai effectué la requête avant de créer la nouvelle entité.


4

à côté de toutes les autres bonnes réponses, cela peut se produire si vous utilisez mergepour conserver un objet et oubliez accidentellement d'utiliser la référence fusionnée de l'objet dans la classe parent. considérons l'exemple suivant

merge(A);
B.setA(A);
persist(B);

Dans ce cas, vous fusionnez Amais oubliez d'utiliser l'objet fusionné de A. pour résoudre le problème, vous devez réécrire le code comme ceci.

A=merge(A);//difference is here
B.setA(A);
persist(B);

3

j'obtiens cette erreur quand j'utilise

getSession().save(object)

mais ça marche sans problème quand j'utilise

getSession().saveOrUpdate(object) 

3

J'ai également fait face à la même situation. En définissant l'annotation suivante au-dessus de la propriété, elle résout l'exception demandée.

L'exception que j'ai rencontrée.

Exception in thread "main" java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.model.Car_OneToMany

Pour surmonter, l'annotation que j'ai utilisée.

    @OneToMany(cascade = {CascadeType.ALL})
    @Column(name = "ListOfCarsDrivenByDriver")
    private List<Car_OneToMany> listOfCarsBeingDriven = new ArrayList<Car_OneToMany>();

Ce qui a poussé Hibernate à lever l'exception:

Cette exception est levée sur votre console car l'objet enfant que j'attache à l'objet parent n'est pas présent dans la base de données à ce moment.

En fournissant @OneToMany(cascade = {CascadeType.ALL}), il indique à Hibernate de les enregistrer dans la base de données tout en enregistrant l'objet parent.


2

Par souci d'exhaustivité: A

org.hibernate.TransientPropertyValueException 

avec message

object references an unsaved transient instance - save the transient instance before flushing

se produira également lorsque vous essayez de conserver / fusionner une entité avec une référence à une autre entité qui se trouve être détachée .


1

Une autre raison possible: dans mon cas, j'essayais de sauver l'enfant avant de sauver le parent, sur une toute nouvelle entité.

Le code était quelque chose comme ça dans un modèle User.java:

this.lastName = lastName;
this.isAdmin = isAdmin;
this.accountStatus = "Active";
this.setNewPassword(password);
this.timeJoin = new Date();
create();

La méthode setNewPassword () crée un enregistrement PasswordHistory et l'ajoute à la collection d'historique dans User. Étant donné que l'instruction create () n'avait pas encore été exécutée pour le parent, elle tentait de l'enregistrer dans une collection d'une entité qui n'avait pas encore été créée. Tout ce que j'avais à faire pour y remédier était de déplacer l'appel setNewPassword () après l'appel à create ().

this.lastName = lastName;
this.isAdmin = isAdmin;
this.accountStatus = "Active";
this.timeJoin = new Date();
create();
this.setNewPassword(password);

1

Il existe une autre possibilité qui peut provoquer cette erreur en veille prolongée. Vous pouvez définir une référence non enregistrée de votre objet Asur une entité attachée Bet vouloir conserver un objet C. Même dans ce cas, vous obtiendrez l'erreur susmentionnée.


1

Si vous utilisez Spring Data JPA, une @Transactionalannotation supplémentaire à votre implémentation de service résoudrait le problème.


1

Je pense que c'est parce que vous avez essayé de faire persister un objet qui a une référence à un autre objet qui n'est pas encore persistant, et donc il essaie du "côté DB" de mettre une référence à une ligne qui n'existe pas


0

Un moyen simple de résoudre ce problème consiste à enregistrer les deux entités. enregistrez d'abord l'entité enfant, puis enregistrez l'entité parent. Parce que l'entité parent dépend de l'entité enfant pour la valeur de clé étrangère.

Ci-dessous un simple examen d'une relation un à un

insert into Department (name, numOfemp, Depno) values (?, ?, ?)
Hibernate: insert into Employee (SSN, dep_Depno, firstName, lastName, middleName, empno) values (?, ?, ?, ?, ?, ?)

Session session=sf.openSession();
        session.beginTransaction();
        session.save(dep);
        session.save(emp);

1
C'est l'opposé - l'entité enfant détient la valeur FK et dépend du parent, vous devez donc d'abord enregistrer le parent! Dans le bloc de code, vous avez raison.
2017

0

Une cause possible de l'erreur est l'inexistence du réglage de la valeur de l'entité parent; Par exemple, pour une relation département-employés, vous devez écrire ceci afin de corriger l'erreur:

Department dept = (Department)session.load(Department.class, dept_code); // dept_code is from the jsp form which you get in the controller with @RequestParam String department
employee.setDepartment(dept);

0

Il y a tellement de possibilités de cette erreur, d'autres possibilités sont également sur la page d'ajout ou la page d'édition. Dans mon cas, j'essayais d'enregistrer un objet AdvanceSalary. Le problème est que lors de la modification, AdvanceSalary employee.employee_id est null car lors de la modification, je n'ai pas défini l'employé.employee_id. J'ai créé un champ caché et je l'ai défini. mon code fonctionne très bien.

    @Entity(name = "ic_advance_salary")
    @Table(name = "ic_advance_salary")
    public class AdvanceSalary extends BaseDO{

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "id")
        private Integer id;

        @ManyToOne(fetch = FetchType.EAGER)
        @JoinColumn(name = "employee_id", nullable = false)
        private Employee employee;

        @Column(name = "employee_id", insertable=false, updatable=false)
        @NotNull(message="Please enter employee Id")
        private Long employee_id;

        @Column(name = "advance_date")
        @DateTimeFormat(pattern = "dd-MMM-yyyy")
        @NotNull(message="Please enter advance date")
        private Date advance_date;

        @Column(name = "amount")
        @NotNull(message="Please enter Paid Amount")
        private Double amount;

        @Column(name = "cheque_date")
        @DateTimeFormat(pattern = "dd-MMM-yyyy")
        private Date cheque_date;

        @Column(name = "cheque_no")
        private String cheque_no;

        @Column(name = "remarks")
        private String remarks;

        public AdvanceSalary() {
        }

        public AdvanceSalary(Integer advance_salary_id) {
            this.id = advance_salary_id;
        }

        public Integer getId() {
            return id;
        }

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

        public Employee getEmployee() {
            return employee;
        }

        public void setEmployee(Employee employee) {
            this.employee = employee;
        }


        public Long getEmployee_id() {
            return employee_id;
        }

        public void setEmployee_id(Long employee_id) {
            this.employee_id = employee_id;
        }

    }

0

J'ai fait face à cette exception lorsque je n'ai pas persisté d'objet parent mais que je sauvais l'enfant. Pour résoudre le problème, avec dans la même session, j'ai persisté les objets enfant et parent et utilisé CascadeType.ALL sur le parent.


0

Cas 1: J'obtenais cette exception lorsque j'essayais de créer un parent et d'enregistrer cette référence parent à son enfant, puis une autre requête DELETE / UPDATE (JPQL). Je viens donc de vider () l'entité nouvellement créée après avoir créé un parent et après avoir créé un enfant en utilisant la même référence parent. Cela a fonctionné pour moi.

Cas 2:

Classe parent

public class Reference implements Serializable {

    @Id
    @Column(precision=20, scale=0)
    private BigInteger id;

    @Temporal(TemporalType.TIMESTAMP)
    private Date modifiedOn;

    @OneToOne(mappedBy="reference")
    private ReferenceAdditionalDetails refAddDetails;
    . 
    .
    .
}

Classe enfant:

public class ReferenceAdditionalDetails implements Serializable{

    private static final long serialVersionUID = 1L;

    @Id
    @OneToOne
    @JoinColumn(name="reference",referencedColumnName="id")
    private Reference reference;

    private String preferedSector1;
    private String preferedSector2;
    .
    .

}

Dans le cas ci-dessus, où le parent (Reference) et l'enfant (ReferenceAdditionalDetails) ont une relation OneToOne et lorsque vous essayez de créer une entité Reference, puis son enfant (ReferenceAdditionalDetails), cela vous donnera la même exception. Donc, pour éviter l'exception, vous devez définir null pour la classe enfant, puis créer le parent. (Exemple de code)

.
.
reference.setRefAddDetails(null);
reference = referenceDao.create(reference);
entityManager.flush();
.
.

0

Mon problème était lié à @BeforeEachJUnit. Et même si j'ai enregistré les entités liées (dans mon cas @ManyToOne), j'ai eu la même erreur.

Le problème est en quelque sorte lié à la séquence que j'ai chez mes parents. Si j'attribue la valeur à cet attribut, le problème est résolu.

Ex. Si j'ai la question d'entité qui peut avoir certaines catégories (une ou plusieurs) et la question d'entité a une séquence:

@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "feedbackSeq")
@Id
private Long id;

Je dois assigner la valeur question.setId(1L);


0

Créez simplement Constructeur de votre mapping dans votre classe de base. Comme si vous voulez une relation un à un dans l'entité A, l'entité B. si vous prenez A comme classe de base, alors A doit avoir un constructeur avec B comme argument.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.