当前位置: 首页 > 知识库问答 >
问题:

Hibernate@Version不适用于一对多

司马同
2023-03-14

我有一个具有一对多关联的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)的表。问题是,当两个用户将相同的子项(使用相同的密钥)添加到相同的父项时,级联保存(session.saveOrUpdate(Parent))失败,子项的主键冲突而不是乐观锁失败。

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

我可以在父类中添加一些虚构的属性,并在每次集合改变时改变它,这样做是可行的,但看起来像一个黑客。

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

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

可能与Hibernate@Version有关,导致数据库外键约束失败。

共有2个答案

易扬
2023-03-14

首先,我认为您需要声明您的主键并定义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;
}

当子项已经存在时,只需执行相同的操作,但不要创建新的子项。

我想这应该能解决你的一些问题。

衡玄裳
2023-03-14

只有单向集合更改将传播到父实体版本。由于您使用的是双向关联,因此将由@manytone端控制此关联,因此在父端集合中添加/删除实体不会影响父实体版本。

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

简而言之,您需要让所有实体实现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配置属性提供RootAware FlushEntityEventListenerIntegrator

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

现在,当您修改PostComment细节实体时:

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

父< code>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
 类似资料:
  • 问题内容: 很抱歉打扰-也许这是一个非常简单的问题- 但由于某些原因,下面的版本无法解析,而带有set的版本可以正常工作。实际上,如果我仅使用set版本并将set替换为list,则会得到: 嵌套异常为org.hibernate.InvalidMappingException:无法从无效映射中解析映射文档 谢谢米莎 问题答案: 你说 而带有set的版本可以正常工作 这是清单DOCTYPE 您可以看到

  • 我对JPareSposition进行了本机查询,如: 生成的查询如下所示: 几何图形是表中包含空间数据的列。 但跟踪还表明查询存在解析错误: 但当我在数据库中执行查询时,我得到的结果是正确的。 为什么这个不匹配? 我最终可以通过以下方式解决这个问题:

  • 问题内容: 我正在使用Hibernate3.3.1,我想在人员和指定公司之间建立关系。他们应该松耦合,但我想安排通过级联创建公司,而不是显式调用saveOrUpdate(newCompany)。 我定义了以下实体: 在我的小岛内,我正在执行以下操作: 我得到一个例外 org.hibernate.TransientObjectException:对象引用了一个未保存的瞬态实例- 在刷新之前保存该瞬态

  • 背景: 最近我一直在开发一个程序,该程序对输入数据文件(用户以完整路径或名称的形式给出,如果它位于程序创建的名为inputFiles的文件夹中)执行一些数据分析并吐出一堆输出数据文件(使用CSV)。 问题: 我遇到的问题是,当我运行时。jar在我的Windows机器上,它将创建3个文件夹(InputFiles、TempFiles和OutPutFiles),但当程序在Mac上时,GUI会启动,但不会

  • 我有以下类层次结构: 我正在尝试读取并锁定一个具体实例,以便其他事务无法读取它,使用hibernate。 现在的函数: 正在工作-为刷新操作生成“SELECT for UPDATE”语法。 refresh()和get()函数之间的不同之处在于get()函数使用外部左联接来选择具体对象,而refresh()使用内部联接来选择具体对象。 在悲观锁定的上下文中,这些连接之间有区别吗?