J'ai une situation dans laquelle j'ai besoin de rattacher des objets détachés à une session de mise en veille prolongée, bien qu'un objet de la même identité PEUT déjà exister dans la session, ce qui provoquera des erreurs.
En ce moment, je peux faire deux choses.
getHibernateTemplate().update( obj )
Cela fonctionne si et seulement si un objet n'existe pas déjà dans la session de mise en veille prolongée. Des exceptions sont levées indiquant qu'un objet avec l'identifiant donné existe déjà dans la session lorsque j'en ai besoin plus tard.getHibernateTemplate().merge( obj )
Cela fonctionne si et seulement si un objet existe dans la session de mise en veille prolongée. Des exceptions sont levées lorsque j'ai besoin que l'objet soit dans une session plus tard si j'utilise ceci.
Compte tenu de ces deux scénarios, comment puis-je attacher de manière générique des sessions à des objets? Je ne veux pas utiliser d'exceptions pour contrôler le flux de la solution de ce problème, car il doit y avoir une solution plus élégante ...
refresh()
les entités détachées? En regardant à travers la spécification 2.0, je ne vois aucune justification; juste que ce n'est pas autorisé.*Reattaching a modified detached instance* A detached instance may be reattached to a new Session (and managed by this new persistence context) by calling update() on the detached object. In our experience, it may be easier for you to understand the following code if you rename the update() method in your mind to reattach()—however, there is a good reason it’s called updating.
Plus d'informations peuvent être trouvées dans la section 9.3.2lock(LockMode.NONE)
peut en fait être appelé sur un objet transitoire, et il rattache l'entité à la session. Voir stackoverflow.com/a/3683370/14379Toutes ces réponses manquent une distinction importante. update () est utilisé pour (re) attacher votre graphe d'objets à une Session. Les objets que vous passez sont ceux qui sont rendus gérés.
merge () n'est en fait pas une API de (re) pièce jointe. Remarquez que merge () a une valeur de retour? C'est parce qu'il vous renvoie le graphique géré, qui n'est peut-être pas le graphique que vous lui avez transmis. merge () est une API JPA et son comportement est régi par la spécification JPA. Si l'objet que vous passez à merge () est déjà géré (déjà associé à la Session), c'est le graphe avec lequel Hibernate fonctionne; l'objet passé est le même objet renvoyé par merge (). Si, cependant, l'objet que vous passez dans merge () est détaché, Hibernate crée un nouveau graphe d'objets qui est 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 la spécification JPA.
En termes de stratégie générique pour «s'assurer que cette entité est gérée, ou la rendre gérée», cela dépend en quelque sorte de si vous souhaitez également prendre en compte les données non encore insérées. En supposant que vous le faites, utilisez quelque chose comme
if ( session.contains( myEntity ) ) { // nothing to do... myEntity is already associated with the session } else { session.saveOrUpdate( myEntity ); }
Remarquez que j'ai utilisé saveOrUpdate () plutôt que update (). Si vous ne voulez pas que les données non encore insérées soient gérées ici, utilisez plutôt update () ...
la source
Session.contains(Object)
contrôles par référence. S'il existe déjà une autre entité représentant la même ligne dans la session et que vous transmettez une instance détachée, vous obtiendrez une exception.Session.contains(Object)
contrôle par référence, s'il y a une autre Entité représentant la même ligne en session, elle retournera false et elle la mettra à jour.Réponse peu diplomatique: vous recherchez probablement un contexte de persistance étendue. C'est l'une des principales raisons derrière le framework Seam ... Si vous avez du mal à utiliser Hibernate au printemps en particulier, consultez cette partie de la documentation de Seam.
Réponse diplomatique: Ceci est décrit dans la documentation Hibernate . Si vous avez besoin de plus de précisions, consultez la section 9.3.2 de Java Persistence with Hibernate intitulée «Working with Detached Objects». Je vous recommande vivement d'obtenir ce livre si vous faites autre chose que CRUD avec Hibernate.
la source
Si vous êtes sûr que votre entité n'a pas été modifiée (ou si vous acceptez que toute modification sera perdue), vous pouvez la rattacher à la session avec verrouillage.
Il ne verrouille rien, mais il récupère l'entité du cache de session ou (s'il n'y est pas trouvé) la lit à partir de la base de données.
Il est très utile d'empêcher LazyInitException lorsque vous naviguez dans les relations à partir d'une "ancienne" entité (de HttpSession par exemple). Vous «reconnectez» d'abord l'entité.
L'utilisation de get peut également fonctionner, sauf lorsque vous obtenez l'héritage mappé (ce qui lèvera déjà une exception sur getId ()).
la source
Session.lock(entity, LockMode.NONE)
échoue avec une exception disant: impossible de réassocier la collection transitoire non initialisée. Comment surmonter cela?Session.find()
méthode API. Peut-être que vous voulez direSession.load(Object object, Serializable id)
.États d'entité
JPA définit les états d'entité suivants:
Nouveau (transitoire)
Un objet nouvellement créé qui n'a jamais été associé à un Hibernate
Session
(akaPersistence Context
) et n'est mappé à aucune ligne de table de base de données est considéré comme étant dans l'état Nouveau (transitoire).Pour devenir persistant, nous devons soit appeler explicitement la
EntityManager#persist
méthode, soit utiliser le mécanisme de persistance transitive.Persistant (géré)
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 la session).
Avec Hibernate, nous n'avons plus besoin d'exécuter des instructions INSERT / UPDATE / DELETE. Hibernate utilise un style de travail transactionnel à écriture différée et les modifications sont synchronisées au tout dernier moment responsable, pendant le
Session
temps de vidage actuel .Détaché
Une fois le contexte de persistance en cours d'exécution fermé, toutes les entités précédemment gérées sont détachées. Les modifications successives ne seront plus suivies et aucune synchronisation automatique de la base de données ne se produira.
Transitions d'état d'entité
Vous pouvez modifier l'état de l'entité à l'aide de diverses méthodes définies par l'
EntityManager
interface.Pour mieux comprendre les transitions d'état des entités JPA, considérez le diagramme suivant:
Lorsque vous utilisez JPA, pour réassocier une entité détachée à un actif
EntityManager
, vous pouvez utiliser l' opération de fusion .Lors de l'utilisation de l'API Hibernate native
merge
, vous pouvez également rattacher une entité détachée à une session Hibernate active à l'aide des méthodes de mise à jour, comme le montre le diagramme suivant:Fusionner une entité détachée
La fusion va copier l'état de l'entité détachée (source) vers une instance d'entité gérée (destination).
Considérez que nous avons persisté l'
Book
entité suivante , et maintenant l'entité est détachée car celleEntityManager
qui a été utilisée pour conserver l'entité s'est fermée:Book _book = doInJPA(entityManager -> { Book book = new Book() .setIsbn("978-9730228236") .setTitle("High-Performance Java Persistence") .setAuthor("Vlad Mihalcea"); entityManager.persist(book); return book; });
Pendant que l'entité est à l'état détaché, nous la modifions comme suit:
_book.setTitle( "High-Performance Java Persistence, 2nd edition" );
Maintenant, nous voulons propager les modifications dans la base de données, afin que nous puissions appeler la
merge
méthode:doInJPA(entityManager -> { Book book = entityManager.merge(_book); LOGGER.info("Merging the Book entity"); assertFalse(book == _book); });
Et Hibernate va exécuter les instructions SQL suivantes:
SELECT b.id, b.author AS author2_0_, b.isbn AS isbn3_0_, b.title AS title4_0_ FROM book b WHERE b.id = 1 -- Merging the Book entity UPDATE book SET author = 'Vlad Mihalcea', isbn = '978-9730228236', title = 'High-Performance Java Persistence, 2nd edition' WHERE id = 1
Si l'entité fusionnée n'a pas d'équivalent dans le courant
EntityManager
, un instantané d'entité frais sera extrait de la base de données.Une fois qu'il y a une entité gérée, JPA copie l'état de l'entité détachée sur celle qui est actuellement gérée, et pendant le contexte de persistance
flush
, une MISE À JOUR sera générée si le mécanisme de vérification sale trouve que l'entité gérée a changé.Rattacher une entité détachée
Hibernate, mais pas JPA, prend en charge la reconnexion via la
update
méthode.Un Hibernate
Session
ne peut associer qu'un seul objet entité pour une ligne de base de données donnée. Cela est dû au fait que le contexte de persistance agit comme un cache en mémoire (cache de premier niveau) et qu'une seule valeur (entité) est associée à une clé donnée (type d'entité et identificateur de base de données).Une entité ne peut être rattachée que s'il n'y a pas d'autre objet JVM (correspondant à la même ligne de base de données) déjà associé au Hibernate actuel
Session
.Considérant que nous avons persisté l'
Book
entité et que nous l'avons modifiée lorsque l'Book
entité était à l'état détaché:Book _book = doInJPA(entityManager -> { Book book = new Book() .setIsbn("978-9730228236") .setTitle("High-Performance Java Persistence") .setAuthor("Vlad Mihalcea"); entityManager.persist(book); return book; }); _book.setTitle( "High-Performance Java Persistence, 2nd edition" );
Nous pouvons rattacher l'entité détachée comme ceci:
doInJPA(entityManager -> { Session session = entityManager.unwrap(Session.class); session.update(_book); LOGGER.info("Updating the Book entity"); });
Et Hibernate exécutera l'instruction SQL suivante:
-- Updating the Book entity UPDATE book SET author = 'Vlad Mihalcea', isbn = '978-9730228236', title = 'High-Performance Java Persistence, 2nd edition' WHERE id = 1
Contrairement à
merge
, l'entité détachée fournie va être réassociée au contexte de persistance actuel et une MISE À JOUR est planifiée pendant le vidage, que l'entité ait été modifiée ou non.Pour éviter cela, vous pouvez utiliser l'
@SelectBeforeUpdate
annotation Hibernate qui déclenchera une instruction SELECT qui récupère l'état chargé qui est ensuite utilisée par le mécanisme de vérification sale.@Entity(name = "Book") @Table(name = "book") @SelectBeforeUpdate public class Book { //Code omitted for brevity }
Méfiez-vous de l'exception NonUniqueObjectException
Un problème qui peut se produire
update
est si le contexte de persistance contient déjà une référence d'entité avec le même identifiant et du même type que dans l'exemple suivant:Book _book = doInJPA(entityManager -> { Book book = new Book() .setIsbn("978-9730228236") .setTitle("High-Performance Java Persistence") .setAuthor("Vlad Mihalcea"); Session session = entityManager.unwrap(Session.class); session.saveOrUpdate(book); return book; }); _book.setTitle( "High-Performance Java Persistence, 2nd edition" ); try { doInJPA(entityManager -> { Book book = entityManager.find( Book.class, _book.getId() ); Session session = entityManager.unwrap(Session.class); session.saveOrUpdate(_book); }); } catch (NonUniqueObjectException e) { LOGGER.error( "The Persistence Context cannot hold " + "two representations of the same entity", e ); }
Maintenant, lors de l'exécution du cas de test ci-dessus, Hibernate va lancer un
NonUniqueObjectException
car le secondEntityManager
contient déjà uneBook
entité avec le même identifiant que celui auquel nous passonsupdate
, et le contexte de persistance ne peut pas contenir deux représentations de la même entité.org.hibernate.NonUniqueObjectException: A different object with the same identifier value was already associated with the session : [com.vladmihalcea.book.hpjp.hibernate.pc.Book#1] at org.hibernate.engine.internal.StatefulPersistenceContext.checkUniqueness(StatefulPersistenceContext.java:651) at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:284) at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:227) at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:92) at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73) at org.hibernate.internal.SessionImpl.fireSaveOrUpdate(SessionImpl.java:682) at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:674)
Conclusion
La
merge
méthode est à privilégier si vous utilisez un verrouillage optimiste car elle vous permet d'éviter la perte de mises à jour. Pour plus de détails sur ce sujet, consultez cet article .Il
update
convient aux mises à jour par lots, car il peut empêcher l'instruction SELECT supplémentaire générée par l'merge
opération, réduisant ainsi le temps d'exécution de la mise à jour par lots.la source
@SelectBeforeUpdate
annotation. Quand la sélection est-elle déclenchée? Lors de l'appelupdate
, juste avant le vidage ou cela n'a pas vraiment d'importance (cela peut être important si hibernate récupère toutes les entités annotées en un seul appel avant le vidage)?@SelectBeforeUpdate
déclenche le SELECT pendant l'flush
opération de contexte de persistance . Consultez lagetDatabaseSnapshot
méthode dans leDefaultFlushEntityEventListener
pour plus de détails.Je suis retourné au JavaDoc pour
org.hibernate.Session
et j'ai trouvé ce qui suit:Ainsi
update()
,saveOrUpdate()
,lock()
,replicate()
etmerge()
sont les options de candidats.update()
: Lèvera une exception s'il existe une instance persistante avec le même identifiant.saveOrUpdate()
: Enregistrer ou mettre à jourlock()
: Obsolètereplicate()
: Conserve l'état de l'instance détachée donnée, en réutilisant la valeur d'identificateur actuelle.merge()
: Renvoie un objet 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é directement et en fonction de l'exigence fonctionnelle, un ou plusieurs d'entre eux peuvent être choisis.la source
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 objet car Contains était toujours false. Le problème est que NHibernate compare les objets par identifiant et type de base de données. Contains utilise la
equals
méthode, qui compare par référence si elle n'est pas écrasée. Avec cetteequals
méthode, 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; }
la source
Session.contains(Object obj)
vérifie la référence et ne détecte pas une instance différente qui représente la même ligne et y 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.
la source
J'ai trouvé une solution pour "rafraîchir" un objet du magasin de persistance qui tiendra compte d'autres objets 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); }
la source
Désolé, impossible d'ajouter des commentaires (encore?).
Utilisation d'Hibernate 3.5.0-Final
Alors que la
Session#lock
méthode de cette dépréciée, la javadoc ne suggère l' utilisationSession#buildLockRequest(LockOptions)#lock(entity)
et si vous assurer que vos associations ontcascade=lock
, le chargement paresseux est pas un problème non plus .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 un régal.
la source
Il se comporte peut-être légèrement différemment sur Eclipselink. Pour rattacher des objets détachés sans obtenir de données obsolètes, je fais généralement:
et en option une deuxième étape (pour invalider les caches):
la source
essayez getHibernateTemplate (). replicate (entité, ReplicationMode.LATEST_VERSION)
la source
Dans le message original, il existe deux méthodes,
update(obj)
etmerge(obj)
qui sont mentionnées pour fonctionner, mais dans des circonstances opposées. Si cela est vraiment vrai, alors pourquoi ne pas tester pour voir si l'objet est déjà dans la session en premier, puis appelerupdate(obj)
si c'est le cas, sinon appelermerge(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); }
la source
pour rattacher cet objet, vous devez utiliser merge ();
cette méthode accepte en paramètre votre entité détachée et retourne une entité sera attachée et rechargée depuis la base de données.
la source
appeler d'abord 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.
la source
La propriété a
hibernate.allow_refresh_detached_entity
fait l'affaire pour moi. Mais c'est une règle générale, donc ce n'est pas très approprié si vous ne voulez le faire que dans certains cas. J'espère que cela aide.Testé sur Hibernate 5.4.9
SessionFactoryOptionsBuilder
la source
Prise en charge de Hibernate: rattachez l'entité détachée par des moyens servals, voir le guide de l'utilisateur d'Hibernate .
la source
try getHibernateTemplate().saveOrUpdate()
la source