2011-08-02 68 views
3

我在使用樂觀鎖定時遇到了麻煩。我有一個持久對象,帶有一個版本號。我希望僅當我的持久對象「真正」更新時,纔會增加此版本號,這意味着一個或多個字段已被修改,或者在數據庫中修改了映射到具有@ManyToOne@OneToMany批註的實體中的集合。 會發生什麼情況是,只有當直接包含在我的實體中的字段發生更改時纔會增加版本,並且不會在集合發生更改時發生休眠樂觀鎖和集合的問題

注意:我把select-before-update放在我的Entity註解中。不知道它是否會改變集合上的版本控制行爲! 我也有一個領域,不應該影響我的實體的版本,我把@OptimisticLock(exclude=true)註釋。

有誰知道我可以如何嘗試使我的版本工作?根據我在幾個論壇上讀到的內容,當收集改變時,版本號應該自動增加。爲什麼我的情況不是這樣?任何想法?

回答

0

您應該將一些參數(CascadeType)添加到@OneToMany註釋中,例如(母公司)

@OneToMany(mappedBy = "parent", cascade = {CascadeType.ALL}) 
@OptimisticLock(excluded = true) 
public Set<Child> getChildren() 
{ 
    return children; 
} 

而且在@ManyToOne(子實體)

@ManyToOne 
@OptimisticLock(excluded = true) 

http://shrubbery.homeip.net/c/display/W/Hibernate+JPA+Tips(域名更改)

1

我想,閱讀文檔,因爲這之後:https://docs.redhat.com/docs/en-US/JBoss_Enterprise_Web_Server/1.0/html/Hibernate_Annotations_Reference_Guide/ch03s04s03s08.html,即@OptimisticLock(excluded = true)防止版本增加。所以在你給的例子中,如果你把它放在映射集合的字段上,它就不會正確運行。

我認爲好的爲例應該是:

@OneToMany(mappedBy = "parent", cascade = {CascadeType.ALL}) 
public Set<Child> getChildren() 
{ 
    return children; 
} 

@OneToMany(mappedBy = "parent", cascade = {CascadeType.ALL}) 
@OptimisticLock(excluded = false) 
public Set<Child> getChildren() 
{ 
    return children; 
} 
+0

請注意,這隻會增加家長的版本時,孩子們都加入或將其從集合中移除,而不是當一個孩子的屬性被修改(我會找到有用的,以保證整體的完整性實體彙總)。 –

0

選擇之前更新只檢索實體實例的狀態,而不是收集和關聯。因此,當您更新集合並更新實體時,更新前的選擇忽略集合更新。您可以嘗試使用加載實體(包括集合)的合併操作,並從分離的實例複製更改,並將持久實體交給您。

1

只有單向收集更改將傳播到父實體版本as explained in this article。因爲您正在使用雙向關聯,所以控制此關聯的是@ManyToOne,因此,在父集合中添加/刪除實體不會影響父實體版本。

但是,您仍然可以將更改從子實體傳播到父實體。這需要您每當修改子實體時傳播OPTIMISTIC_FORCE_INCREMENT鎖。

This article詳細解釋了你應該實現這種用例的方式。

總之,你需要把所有的實體實施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(); 
    } 
} 

然後,你需要兩個事件偵聽器:

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); 
    } 
} 

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); 
    } 
} 

這你可以註冊如下:

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 
    } 
} 

,然後通過一個Hibernate配置屬性提供RootAwareFlushEntityEventListenerIntegrator

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

現在,當你修改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); 

Post實體版本的修改,以及:

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 

PostComment實體也是如此。

如果你插入一個新的子實體它的工作原理,甚至:

Post post = entityManager.getReference(Post.class, 1L); 

PostComment postComment = new PostComment(); 
postComment.setId(3L); 
postComment.setReview("Worth it!"); 
postComment.setPost(post); 
entityManager.persist(postComment); 

休眠管理來增加適當父實體:

SELECT p.id AS id1_0_0_ , 
     p.title AS title2_0_0_ , 
     p.version AS version3_0_0_ 
FROM post p 
WHERE p.id = 1 

INSERT INTO post_comment (post_id, review, id) 
VALUES (1, 'Worth it!', 3) 

UPDATE post 
SET version = 3 
WHERE id = 1 AND version = 2 

刪除子實體時,它也可以工作:

PostComment postComment = entityManager.getReference(PostComment.class, 3l); 
entityManager.remove(postComment); 

Hibernate管理增加本用例中的父實體:

SELECT pc.id AS id1_1_0_ , 
     pc.post_id AS post_id3_1_0_ , 
     pc.review AS review2_1_0_ 
FROM post_comment pc 
WHERE pc.id = 3 

SELECT p.id AS id1_0_0_ , 
     p.title AS title2_0_0_ , 
     p.version AS version3_0_0_ 
FROM post p 
WHERE p.id = 1 

DELETE FROM post_comment 
WHERE id = 3 

UPDATE post 
SET version = 4 
WHERE id = 1 and version = 3