Hibernate @Version不适用于一对多

时间:2014-01-30 08:50:01

标签: java oracle hibernate

我有一个具有一对多关联的hibernate实体:

@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(使用相同的密钥)添加到同一个Parent时,级联保存(session.saveOrUpdate(父))因Child的主键违规而不是乐观锁定失败而失败。

如果用户除了集合之外还更改了Parent实体中的其他一些属性,则乐观锁可以正常工作。

我可以向Parent类添加一些虚构属性,并在每次集合更改时更改它,它会完成这个技巧,但它看起来像是黑客。

或者我可以将复合主键替换为代理主键(通过添加@Id)。

问题是:在这种情况下实施乐观锁定的推荐方法是什么?

可能与Hibernate @Version causing database foreign key constraint failure有关。

2 个答案:

答案 0 :(得分:1)

只有单向集合更改才会传播到父实体版本as explained in this article。因为您正在使用双向关联,所以;WITH var_varattr (FK_Variation_ID) As ( SELECT FK_Variation_ID FROM T_Variation_VariationAttribute WHERE ID IN ( SELECT TOP 100 PERCENT FK_Variation_VariationAttribute_ID FROM T_Artikel_T_Variation WHERE FK_Artikel_ID = 46 ORDER BY Position ASC ) ) SELECT Id, Name FROM T_Variation WHERE ID IN ( SELECT FK_Variation_ID FROM var_varattr ) 方将控制此关联,因此在父方集合中添加/删除实体不会影响父实体版本。

但是,您仍然可以将更改从子实体传播到父实体。这要求您在修改子实体时传播OPTIMISTIC_FORCE_INCREMENT锁。

This article详细解释了您应该如何实现这样一个用例。

简而言之,您需要让所有实体实现@ManyToOne接口:

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

然后通过Hibernate配置属性提供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 } }

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

答案 1 :(得分: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;
}

当孩子已经存在时,只需做同样的事情,但不要创建新的孩子。

我认为它应该解决你的一些问题。