java - Hibernate @Version 不适用于一对多

标签 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)的表上。问题是,当两个用户将相同的子级(具有相同的 key )添加到相同的父级时,级联保存(session.saveOrUpdate(parent))会因子级主键冲突而失败,而不是乐观锁失败。

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

我可以向 Parent 类添加一些虚构的属性,并在每次集合更改时更改它,这样就可以达到目的,但看起来像是一个 hack。

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

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

可能与Hibernate @Version causing database foreign key constraint failure相关.

最佳答案

只有单向集合更改才会是 propagated to the parent entity version 。由于您使用的是双向关联,因此 @ManyToOne 端将控制此关联,因此在父端集合中添加/删除实体不会影响父实体版本。

但是,您仍然可以将更改从子实体传播到父实体。这需要您传播OPTIMISTIC_FORCE_INCREMENT每当子实体被修改时就锁定。

简而言之,您需要让所有实体实现 RootAware接口(interface):

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

关于java - Hibernate @Version 不适用于一对多,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21451601/

相关文章:

java - Swagger 2 接受 xml 而不是 json

java - 如何使用注释在 Hibernate 中表示复合键?

java - 具有一个公共(public)变量的 getter 和 setter 问题

java - 如何获取用户输入中给出的位数

string - 如何在 Oracle 中拆分逗号分隔的字符串

oracle - 如何对任何变量应用约束,使其在oracle中只需要5个数字字符

java - 如何从Java中的错误消息中去除Oracle错误代码?

java - 错误消息 : Exception in thread "main" org. hibernate.HibernateException:访问 stax 流时出错

java - 有没有办法以可配置的方式将字段视为@Transient?

java - 如何将字节数组转换为 double 组?