Quelle est la bonne façon de rattacher des objects détachés dans Hibernate?

J’ai une situation dans laquelle je dois rattacher des objects détachés à une session d’hibernation, bien qu’un object de même identité puisse déjà exister dans la session, ce qui entraînera des erreurs.

En ce moment, je peux faire deux choses.

  1. getHibernateTemplate().update( obj ) Cela fonctionne si et seulement si un object n’existe pas déjà dans la session d’hibernation. Des exceptions sont émises indiquant qu’un object avec l’identificateur donné existe déjà dans la session lorsque j’en ai besoin ultérieurement.

  2. getHibernateTemplate().merge( obj ) Cela fonctionne si et seulement si un object existe dans la session d’hibernation. Des exceptions sont levées lorsque j’ai besoin de l’object pour être dans une session plus tard si je l’utilise.

Compte tenu de ces deux scénarios, comment puis-je associer de manière générique des sessions à des objects? Je ne veux pas utiliser les exceptions pour contrôler le stream de la solution de ce problème, car il doit y avoir une solution plus élégante …

Il semble donc qu’il n’existe aucun moyen de rattacher une entité détachée à JPA.

merge() poussera l’état obsolète vers la firebase database et remplacera toutes les mises à jour intermédiaires.

refresh() ne peut pas être appelé sur une entité détachée.

lock() ne peut pas être appelée sur une entité détachée, et même si elle le pouvait, et elle a réattaché l’entité, en appelant “lock” avec l’argument “LockMode.NONE”, cela signifie que vous verrouillez, mais pas verrouillez de la conception de l’API que j’ai jamais vu.

Donc vous êtes coincé Il y a une méthode detach() , mais pas attach() ou reattach() . Une étape évidente du cycle de vie de l’object n’est pas disponible pour vous.

À en juger par le nombre de questions similaires sur JPA, il semble que même si JPA prétend avoir un modèle cohérent, cela ne correspond certainement pas au modèle mental de la plupart des programmeurs, qui sont maudits de perdre des heures à comprendre comment obtenir JPA doit faire les choses les plus simples et se retrouver avec du code de gestion du cache sur toutes ses applications.

Il semble que la seule façon de le faire soit de supprimer votre entité détachée périmée et de faire une requête de recherche avec le même identifiant, qui atteindra la L2 ou la DB.

Mik

Toutes ces réponses manquent une distinction importante. update () est utilisé pour (re) attacher votre graphe d’object à une session. Les objects que vous lui transmettez sont ceux qui sont gérés.

merge () n’est en fait pas une (re) pièce jointe. Avis merge () a une valeur de retour? C’est parce qu’il vous renvoie le graphe géré, qui n’est peut-être pas le graphe que vous avez passé. merge () est une API JPA et son comportement est régi par la spécification JPA. Si l’object que vous transmettez pour fusionner () est déjà géré (déjà associé à la session), c’est le graphique avec lequel Hibernate fonctionne; l’object passé est le même object renvoyé par merge (). Si, toutefois, l’object que vous passez dans merge () est détaché, Hibernate crée un nouveau graphe d’object géré et copie l’état de votre graphe détaché sur le nouveau graphe géré. Encore une fois, tout cela est dicté et régi par les spécifications JPA.

En termes de stratégie générique pour “s’assurer que cette entité est gérée ou gérée”, cela dépend si vous souhaitez également comptabiliser les données non encore insérées. En supposant que vous faites, utilisez quelque chose comme

 if ( session.contains( myEntity ) ) { // nothing to do... myEntity is already associated with the session } else { session.saveOrUpdate( myEntity ); } 

Notez que j’ai utilisé saveOrUpdate () plutôt que update (). Si vous ne voulez pas que des données non encore insérées soient manipulées ici, utilisez plutôt update () …

Réponse non diplomatique: Vous recherchez probablement un contexte de persistance étendu. C’est l’une des raisons principales du framework Seam … Si vous avez du mal à utiliser Hibernate in Spring en particulier, jetez un coup d’œil à ce document de Seam.

Réponse diplomatique: Ceci est décrit dans la documentation d’Hibernate . Si vous avez besoin de plus de précisions, consultez la section 9.3.2 de Java Persistence with Hibernate intitulée “Travailler avec des objects détachés”. Je vous recommande fortement de vous procurer ce livre si vous faites autre chose que CRUD avec Hibernate.

Si vous êtes certain que votre entité n’a pas été modifiée (ou si vous acceptez qu’une modification soit perdue), vous pouvez la rattacher à la session avec le locking.

 session.lock(entity, LockMode.NONE); 

Il ne verrouille rien, mais il récupérera l’entité du cache de la session ou (si elle n’y est pas trouvée) la lira depuis la firebase database.

Il est très utile d’empêcher LazyInitException lorsque vous naviguez dans des relations à partir d’une “ancienne” entité (par exemple, la session HttpSession). Vous devez d’abord “rattacher” l’entité.

L’utilisation de get peut également fonctionner, sauf si vous obtenez l’inheritance mappé (qui lancera déjà une exception sur le getId ()).

 entity = session.get(entity.getClass(), entity.getId()); 

Je suis retourné à JavaDoc pour org.hibernate.Session et j’ai trouvé ce qui suit:

Les instances transitoires peuvent être rendues persistantes en appelant save() , persist() ou saveOrUpdate() . Les instances persistantes peuvent être rendues transitoires en appelant delete() . Toute instance renvoyée par une méthode get() ou load() est persistante. Les instances détachées peuvent être rendues persistantes en appelant update() , saveOrUpdate() , lock() ou replicate() . L’état d’une instance transitoire ou détachée peut également devenir persistant en tant que nouvelle instance persistante en appelant merge() .

Ainsi, update() , saveOrUpdate() , lock() , replicate() et merge() sont les options candidates.

update() : lancera une exception s’il existe une instance persistante avec le même identifiant.

saveOrUpdate() : sauvegarde ou mise à jour

lock() : obsolète

replicate() : persiste l’état de l’instance détachée donnée, en réutilisant la valeur de l’identifiant actuel.

merge() : Retourne un object persistant avec le même identifiant. L’instance donnée n’est pas associée à la session.

Par conséquent, lock() ne doit pas être utilisé immédiatement et, en fonction des exigences fonctionnelles, un ou plusieurs d’entre eux peuvent être choisis.

Je l’ai fait de cette façon en C # avec NHibernate, mais cela devrait fonctionner de la même manière en Java:

 public virtual void Attach() { if (!HibernateSessionManager.Instance.GetSession().Contains(this)) { ISession session = HibernateSessionManager.Instance.GetSession(); using (ITransaction t = session.BeginTransaction()) { session.Lock(this, NHibernate.LockMode.None); t.Commit(); } } } 

First Lock a été appelé sur chaque object car Contains était toujours faux. Le problème est que NHibernate compare les objects par identifiant et type de firebase database. Contient utilise la méthode equals , qui se compare par référence si elle n’est pas écrasée. Avec cette méthode equals , cela fonctionne sans aucune exception:

 public override bool Equals(object obj) { if (this == obj) { return true; } if (GetType() != obj.GetType()) { return false; } if (Id != ((BaseObject)obj).Id) { return false; } return true; } 

Session.contains(Object obj) vérifie la référence et ne détectera pas une autre instance qui représente la même ligne et qui lui est déjà attachée.

Voici ma solution générique pour les entités avec une propriété d’identifiant.

 public static void update(final Session session, final Object entity) { // if the given instance is in session, nothing to do if (session.contains(entity)) return; // check if there is already a different attached instance representing the same row final ClassMetadata classMetadata = session.getSessionFactory().getClassMetadata(entity.getClass()); final Serializable identifier = classMetadata.getIdentifier(entity, (SessionImplementor) session); final Object sessionEntity = session.load(entity.getClass(), identifier); // override changes, last call to update wins if (sessionEntity != null) session.evict(sessionEntity); session.update(entity); } 

C’est l’un des rares aspects de .Net EntityFramework que j’aime, les différentes options d’attachement concernant les entités modifiées et leurs propriétés.

Je suis venu avec une solution pour “rafraîchir” un object du magasin de persistance qui tiendra compte d’autres objects qui peuvent déjà être attachés à la session:

 public void refreshDetached(T entity, Long id) { // Check for any OTHER instances already attached to the session since // refresh will not work if there are any. T attached = (T) session.load(getPersistentClass(), id); if (attached != entity) { session.evict(attached); session.lock(entity, LockMode.NONE); } session.refresh(entity); } 

Désolé, ne semble pas pouvoir append de commentaires (encore?).

Utiliser Hibernate 3.5.0-Final

Alors que la méthode de Session#lock obsolète, le javadoc suggère d’utiliser Session#buildLockRequest(LockOptions)#lock(entity) et si vous vous assurez que vos associations ont cascade=lock , le chargement différé n’est pas non plus un problème.

Donc, ma méthode d’attachement ressemble un peu

 MyEntity attach(MyEntity entity) { if(getSession().contains(entity)) return entity; getSession().buildLockRequest(LockOptions.NONE).lock(entity); return entity; 

Les premiers tests suggèrent que cela fonctionne bien.

essayez getHibernateTemplate (). replicate (entity, ReplicationMode.LATEST_VERSION)

Dans le message original, il existe deux méthodes, update(obj) et merge(obj) qui sont mentionnées pour fonctionner, mais dans des circonstances opposées. Si cela est vraiment vrai, alors pourquoi ne pas tester si l’object est déjà dans la session en premier, puis appeler update(obj) si c’est le cas, sinon appelez merge(obj) .

Le test d’existence dans la session est session.contains(obj) . Par conséquent, je pense que le pseudo-code suivant fonctionnerait:

 if (session.contains(obj)) { session.update(obj); } else { session.merge(obj); } 

Peut-être qu’il se comporte légèrement différemment sur Eclipselink. Pour rattacher des objects sans obtenir de données obsolètes, je le fais généralement:

 Object obj = em.find(obj.getClass(), id); 

et en option une deuxième étape (pour faire invalider les caches):

 em.refresh(obj) 

pour rattacher cet object, vous devez utiliser merge ();

cette méthode accepte en paramètre votre entité détachée et renvoie une entité qui sera jointe et rechargée depuis Database.

 Example : Lot objAttach = em.merge(oldObjDetached); objAttach.setEtat(...); em.persist(objAttach); 

appeler first merge () (pour mettre à jour l’instance persistante), puis verrouiller (LockMode.NONE) (pour attacher l’instance actuelle, pas celle renvoyée par merge ()) semble fonctionner pour certains cas d’utilisation.

 try getHibernateTemplate().saveOrUpdate()