web-dev-qa-db-fra.com

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

J'ai une situation dans laquelle je dois attacher à nouveau des objets détachés à une session d'hibernation, bien qu'un objet de la même identité PEUT déjà exister dans la session, ce qui provoquera des erreurs.

À l'heure actuelle, je peux faire l'une des deux choses.

  1. getHibernateTemplate().update( obj ) Ceci fonctionne si et seulement si un objet n'existe pas déjà dans la session de veille prolongée. Des exceptions sont émises en indiquant qu'un objet avec l'identifiant donné existe déjà dans la session lorsque j'en ai besoin plus tard.

  2. getHibernateTemplate().merge( obj ) Cela fonctionne si et seulement si un objet existe dans la session de veille prolongée. Des exceptions sont levées lorsque j'ai besoin que l'objet soit dans une session ultérieurement si je l'utilise.

Étant donné 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 ...

177
Stefan Kendall

Il semble donc qu’il n’ya aucun moyen de rattacher une entité détachée périmée dans JPA.

merge() transmettra l'état périmé à la base de données et écrasera toutes les mises à jour intervenues.

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

lock() ne peut pas être appelé sur une entité séparée, et même si elle le pouvait, et si elle l'a réattachée, appeler 'lock' avec l'argument 'LockMode.NONE', ce qui implique que vous verrouillez, mais ne verrouillez pas. morceau contre-intuitif de la conception API que j'ai jamais vu.

Donc vous êtes coincé. Il y a une méthode detach(), mais pas attach() ni reattach(). Une étape évidente du cycle de vie d'un objet ne vous est pas accessible.

À 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, il ne correspond certainement pas au modèle mental de la plupart des programmeurs, qui ont été maudits à perdre de nombreuses heures à essayer de comprendre comment obtenir JPA doit faire les choses les plus simples et se retrouver avec du code de gestion de cache dans toutes ses applications.

Il semble que le seul moyen de le faire est de supprimer votre entité détachée obsolète et d'effectuer une requête de recherche avec le même identifiant, qui atteindra le L2 ou la base de données.

Mik

174
mikhailfranco

Toutes ces réponses manquent une distinction importante. update () est utilisé pour (re) lier votre graphe d'objet à une session. Les objets que vous passez sont ceux qui sont gérés.

merge () n'est en réalité pas une API de (re) pièce jointe. Remarquez que merge () a une valeur de retour? C'est parce qu'il vous renvoie le graphe géré, qui peut ne pas être le graphe auquel vous l'avez transmis. merge () est une API JPA et son comportement est régi par la spécification JPA. Si l'objet que vous transmettez à merge () est déjà géré (déjà associé à la session), c'est le graphe avec lequel le graphe fonctionne. l'objet transmis est le même objet renvoyé par merge (). Si, toutefois, l'objet que vous transmettez à merge () est détaché, Hibernate crée un nouveau graphe d'objet 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 de l'APP.

En termes de stratégie générique pour "s’assurer que cette entité est gérée, ou la faire gérer", 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 fassiez, 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 les données non encore insérées soient gérées ici, utilisez update () à la place ...

25
Steve Ebersole

Réponse non-automatique: Vous recherchez probablement un contexte de persistance étendu. C'est l'une des principales raisons derrière Seam Framework ... Si vous avez du mal à utiliser Hibernate au printemps en particulier, consultez cette pièce de la documentation de Seam.

Réponse diplomatique: Ceci est décrit dans le documents Hibernate . Pour plus de précisions, consultez la section 9.3.2 de Java Persistence with Hibernate intitulée "Utilisation d’objets détachés". Je vous recommanderais vivement si vous faites autre chose que CRUD avec Hibernate.

19
cwash

Si vous êtes sûr que votre entité n'a pas été modifiée (ou si vous acceptez que toute modification soit perdue), vous pouvez le rattacher à la session avec un verrou.

session.lock(entity, LockMode.NONE);

Il ne verrouillera rien, mais il obtiendra l'entité à partir du cache de session ou (si elle n'y est pas trouvée) à lire à partir de la base de données.

Il est très utile d'empêcher LazyInitException lorsque vous naviguez dans des relations à partir d'une "ancienne" entité (à partir de HttpSession par exemple). Vous commencez par "rattacher" l'entité.

L'utilisation de get peut également fonctionner, sauf lorsque l'héritage est mappé (ce qui déclenche déjà une exception sur getId ()).

entity = session.get(entity.getClass(), entity.getId());
12
John Rizzo

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 être rendu persistant en tant que nouvelle instance persistante en appelant merge().

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

update(): Lève une exception s'il existe une instance persistante avec le même identifiant.

saveOrUpdate(): soit sauvegarder soit mettre à jour

lock(): Obsolète

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

merge(): renvoie un objet persistant avec le même identifiant. L'instance donnée ne devient pas associée à la session.

Par conséquent, lock() ne doit pas être utilisé directement et un ou plusieurs d'entre eux peuvent être choisis en fonction des besoins fonctionnels.

9
Amitabha Roy

Je l’ai fait de cette façon en C # avec NHibernate, mais cela devrait fonctionner de la même façon 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 faux. Le problème est que NHibernate compare les objets par ID et type de base de données. Contains utilise la méthode equals, qui 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;
}
7
Verena Haunschmid

Session.contains(Object obj) vérifie la référence et ne détectera pas une instance différente qui représente la même ligne et qui 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 bien, les différentes options d’attachement concernant les entités modifiées et leurs propriétés.

4
djmj

Je suis venu avec une solution pour "actualiser" 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);
}
3
WhoopP

Désolé, impossible d'ajouter des commentaires (pour le moment?).

Utilisation d'Hibernate 3.5.0-Final

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

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 tests initiaux suggèrent que cela fonctionne un régal.

2
Gwaptiva

Peut-être que son comportement est légèrement différent sur Eclipselink. Pour rattacher des objets détachés sans obtenir des données obsolètes, je fais habituellement:

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

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

em.refresh(obj)
2
Hartmut P.

Dans le message d'origine, il existe deux méthodes, update(obj) et merge(obj), dont le fonctionnement est indiqué, 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 d'abord, puis appeler update(obj) si c'est le cas, sinon appeler merge(obj).

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

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

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

cette méthode accepte en paramètre votre entité détachée et renvoie une entité qui sera attachée et rechargée depuis la base de données.

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

try getHibernateTemplate (). replicate (entité, ReplicationMode.LATEST_VERSION)

1
Pavitar Singh

l'appelant en premier merge () (pour mettre à jour l'instance persistante), puis lock (LockMode.NONE) (pour attacher l'instance actuelle, pas celle renvoyée par merge ()) semble fonctionner pour certains cas d'utilisation.

0
cheesus