2010-05-24 5 views
4

Considérons deux entités Parent et Child.Entité parent Hibernate versioning

  • enfant fait partie de la collection transitoire des parents
  • enfant a une cartographie ManyToOne pour parent avec FetchType.LAZY

Les deux sont affichés sur le même formulaire à un utilisateur. Lorsque l'utilisateur enregistre les données, nous mettons d'abord à jour l'instance parent, puis la collection enfant (les deux en utilisant la fusion).

Maintenant vient la partie difficile. Lorsque l'utilisateur modifie uniquement la propriété enfant sur le formulaire, la vérification de mise hors tension hibernate ne met pas à jour l'instance parent et n'augmente donc pas le numéro de version de verrouillage optimiste pour cette entité.

Je voudrais voir une situation où seul Parent est versionné et chaque fois que j'appelle fusion pour Parent alors la version est toujours mise à jour même si la mise à jour n'est pas exécutée dans db.

+0

Vous aimeriez peut-être répondre à votre propre question dans ce cas. Quelqu'un pourrait venir voter pour vous. Ou même mieux, cela pourrait aider quelqu'un qui cherche la même chose. –

Répondre

3

Je pense que je l'ai compris. Une fois la fusion appelée, une référence d'instance attachée est renvoyée. Quand j'obtiens un verrou explicite pour cela en utilisant entityManager.lock (mis à jour, LockModeType.WRITE); le numéro de version est alors augmenté même si l'instance parent n'a pas été mise à jour dans db.

En outre, je compare la version d'instance détachée avec la version d'instance persistante. Si elles ne correspondent pas, Parent a été mis à jour dans db et le numéro de version a également changé. Cela maintient les numéros de version cohérents. Autrement entityManager.lock augmenterait le numéro de version même si l'opération de fusion l'a changé.

Toujours à la recherche de solutions pour faire en sorte que hibernate augmente la version lorsque l'entité n'est pas sale pendant la fusion.

+0

+1 pour solution de verrouillage – oedo

0

Je ne pense pas que vous pouvez forcer hibernate à augmenter un numéro de version pour un objet non modifié, car il ne fera tout simplement aucune requête db UPDATE si rien n'a changé (pour des raisons évidentes).

vous pourriez faire un vilain hack comme ajouter un nouveau champ à l'objet et l'augmenter manuellement, mais personnellement, cela semble être une perte de temps et de ressources. J'irais avec votre solution de verrouillage explicite puisque cela semble vous donner ce que vous voulez, sans hackery inutiles.

1

Vous pouvez propager des modifications d'entités enfants vers des entités parent. Cela vous oblige à propager le verrou OPTIMISTIC_FORCE_INCREMENT chaque fois que l'entité enfant est modifiée.

This article explique en détail comment vous devriez implémenter un tel cas d'utilisation.

En bref, vous devez avoir toutes vos entités mettre en œuvre une interface RootAware:

public interface RootAware<T> { 
    T root(); 
} 

@Entity(name = "Post") 
@Table(name = "post") 
public class Post { 

    @Id 
    private Long id; 

    private String title; 

    @Version 
    private int version; 

    //Getters and setters omitted for brevity 
} 

@Entity(name = "PostComment") 
@Table(name = "post_comment") 
public class PostComment 
    implements RootAware<Post> { 

    @Id 
    private Long id; 

    @ManyToOne(fetch = FetchType.LAZY) 
    private Post post; 

    private String review; 

    //Getters and setters omitted for brevity 

    @Override 
    public Post root() { 
     return post; 
    } 
} 

@Entity(name = "PostCommentDetails") 
@Table(name = "post_comment_details") 
public class PostCommentDetails 
    implements RootAware<Post> { 

    @Id 
    private Long id; 

    @ManyToOne(fetch = FetchType.LAZY) 
    @MapsId 
    private PostComment comment; 

    private int votes; 

    //Getters and setters omitted for brevity 

    @Override 
    public Post root() { 
     return comment.getPost(); 
    } 
} 

Ensuite, vous avez besoin de deux écouteurs d'événements:

public static class RootAwareInsertEventListener 
    implements PersistEventListener { 

    private static final Logger LOGGER = 
     LoggerFactory.getLogger(RootAwareInsertEventListener.class); 

    public static final RootAwareInsertEventListener INSTANCE = 
     new RootAwareInsertEventListener(); 

    @Override 
    public void onPersist(PersistEvent event) throws HibernateException { 
     final Object entity = event.getObject(); 

     if(entity instanceof RootAware) { 
      RootAware rootAware = (RootAware) entity; 
      Object root = rootAware.root(); 
      event.getSession().lock(root, LockMode.OPTIMISTIC_FORCE_INCREMENT); 

      LOGGER.info("Incrementing {} entity version because a {} child entity has been inserted", root, entity); 
     } 
    } 

    @Override 
    public void onPersist(PersistEvent event, Map createdAlready) 
     throws HibernateException { 
     onPersist(event); 
    } 
} 

et

public static class RootAwareInsertEventListener 
    implements PersistEventListener { 

    private static final Logger LOGGER = 
     LoggerFactory.getLogger(RootAwareInsertEventListener.class); 

    public static final RootAwareInsertEventListener INSTANCE = 
     new RootAwareInsertEventListener(); 

    @Override 
    public void onPersist(PersistEvent event) throws HibernateException { 
     final Object entity = event.getObject(); 

     if(entity instanceof RootAware) { 
      RootAware rootAware = (RootAware) entity; 
      Object root = rootAware.root(); 
      event.getSession().lock(root, LockMode.OPTIMISTIC_FORCE_INCREMENT); 

      LOGGER.info("Incrementing {} entity version because a {} child entity has been inserted", root, entity); 
     } 
    } 

    @Override 
    public void onPersist(PersistEvent event, Map createdAlready) 
     throws HibernateException { 
     onPersist(event); 
    } 
} 

qui vous pouvez vous inscrire comme suit:

public class RootAwareEventListenerIntegrator 
    implements org.hibernate.integrator.spi.Integrator { 

    public static final RootAwareEventListenerIntegrator INSTANCE = 
     new RootAwareEventListenerIntegrator(); 

    @Override 
    public void integrate(
      Metadata metadata, 
      SessionFactoryImplementor sessionFactory, 
      SessionFactoryServiceRegistry serviceRegistry) { 

     final EventListenerRegistry eventListenerRegistry = 
       serviceRegistry.getService(EventListenerRegistry.class); 

     eventListenerRegistry.appendListeners(EventType.PERSIST, RootAwareInsertEventListener.INSTANCE); 
     eventListenerRegistry.appendListeners(EventType.FLUSH_ENTITY, RootAwareUpdateAndDeleteEventListener.INSTANCE); 
    } 

    @Override 
    public void disintegrate(
      SessionFactoryImplementor sessionFactory, 
      SessionFactoryServiceRegistry serviceRegistry) { 
     //Do nothing 
    } 
} 

puis fournissent la RootAwareFlushEntityEventListenerIntegrator via une propriété de configuration Hibernate:

configuration.put(
    "hibernate.integrator_provider", 
    (IntegratorProvider)() -> Collections.singletonList(
     RootAwareEventListenerIntegrator.INSTANCE 
    ) 
); 

Maintenant, lorsque vous modifiez une entité PostCommentDetails:

PostCommentDetails postCommentDetails = entityManager.createQuery(
    "select pcd " + 
    "from PostCommentDetails pcd " + 
    "join fetch pcd.comment pc " + 
    "join fetch pc.post p " + 
    "where pcd.id = :id", PostCommentDetails.class) 
.setParameter("id", 2L) 
.getSingleResult(); 

postCommentDetails.setVotes(15); 

La version de l'entité mère Post est modifiée ainsi:

SELECT pcd.comment_id AS comment_2_2_0_ , 
     pc.id AS id1_1_1_ , 
     p.id AS id1_0_2_ , 
     pcd.votes AS votes1_2_0_ , 
     pc.post_id AS post_id3_1_1_ , 
     pc.review AS review2_1_1_ , 
     p.title AS title2_0_2_ , 
     p.version AS version3_0_2_ 
FROM post_comment_details pcd 
INNER JOIN post_comment pc ON pcd.comment_id = pc.id 
INNER JOIN post p ON pc.post_id = p.id 
WHERE pcd.comment_id = 2 

UPDATE post_comment_details 
SET votes = 15 
WHERE comment_id = 2 

UPDATE post 
SET version = 1 
where id = 1 AND version = 0