2014-01-30 22 views
1

我有一個休眠實體與一個一對多的關聯關係:休眠@Version不工作,一個一對多

@Entity 
public class Parent { 
    @OneToMany(mappedBy = "parent", fetch = FetchType.LAZY) 
    @Cascade(CascadeType.ALL) 
    private Set<Child> children = new HashSet<Child>(); 

    @Version 
    private Date version; 
} 

@Entity 
public class Child { 
    @ManyToOne(fetch = FetchType.LAZY) 
    @JoinColumn(name = "PARENT_ID") 
    private Parent parent; 

    @Basic 
    private String key; 
} 

*一些註釋爲清楚起見移除

兒童實體上的映射包含複合主鍵(KEY和PARENT_ID)的表。問題是當兩個用戶將同一個Child(具有相同的鍵)添加到同一個父級聯級聯保存(session.saveOrUpdate(父級))時,會導致Child的主鍵違規,而不是樂觀鎖定失敗。

如果除集合外,用戶還在Parent實體中更改其他一些屬性,樂觀鎖定將正常工作。

我可以向父類添加一些虛構屬性,並在集合發生變化時進行更改,它會執行這個技巧,但看起來像是一種黑客攻擊。

或者我可以將複合主鍵替換爲代理項(通過添加@Id)。

問題是:在這種情況下實施樂觀鎖定的推薦方法是什麼?

可能與Hibernate @Version causing database foreign key constraint failure有關。

回答

0

首先我想你需要聲明你的主鍵並且定義PK是如何生成的。 例子:

@Id 
@GeneratedValue(strategy = GenerationType.AUTO) 
@Column(name = "id") 
private Long id; 

然後,新的子項添加到您的父母應該是這樣的(父方)的最佳方式:

public Child addChild() { 
      Child child = new Child() 
    if (childList== null) { 
     childList= new ArrayList<childList>(); 
    } 
    child.setparent(this); 
    childList.add(child); 
    return child; 
} 

當孩子已經存在,只是做同樣的但沒有創建一個新的孩子。

我認爲它應該可以解決您的一些問題。

+0

謝謝。問題是兩個不同的用戶(來自兩個不同的線程)將兩個子實體添加到兩個不同的列表中,並且不可能檢查是否存在。 – ike3

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