Java Hibernate版本控制父实体

Java Hibernate版本控制父实体,java,hibernate,jpa,transactions,locking,Java,Hibernate,Jpa,Transactions,Locking,考虑两个实体:父实体和子实体 子对象是父对象的临时集合的一部分 子项具有到具有FetchType.LAZY的父项的多通映射 对用户而言,这两个选项都显示在同一表单上。当用户保存数据时,我们首先更新父实例,然后更新子集合(都使用合并) 现在是棘手的部分。当用户修改表单上的独子属性时,hibernate脏检查不会更新父实例,因此不会增加该实体的乐观锁定版本号 我希望看到这样的情况,即只有父级进行版本控制,并且每次我为父级调用merge时,版本总是会更新,即使实际的更新没有在数据库中执行。我想我已

考虑两个实体:父实体和子实体

  • 子对象是父对象的临时集合的一部分
  • 子项具有到具有FetchType.LAZY的父项的多通映射
对用户而言,这两个选项都显示在同一表单上。当用户保存数据时,我们首先更新父实例,然后更新子集合(都使用合并)

现在是棘手的部分。当用户修改表单上的独子属性时,hibernate脏检查不会更新父实例,因此不会增加该实体的乐观锁定版本号


我希望看到这样的情况,即只有父级进行版本控制,并且每次我为父级调用merge时,版本总是会更新,即使实际的更新没有在数据库中执行。

我想我已经解决了这个问题。调用合并后,将返回一个附加的实例引用。当我使用entityManager.lock(更新,LockModeType.WRITE)获取该文件的显式锁时;即使父实例未在数据库中更新,版本号也会增加

此外,我正在比较分离实例版本和持久化实例版本。如果它们不匹配,则在db中更新了父级,并且版本号也已更改。这使版本号保持一致。否则,即使合并操作更改了entityManager.lock的版本号,它也会增加版本号


仍在寻找解决方案,如何使hibernate在合并期间在实体不脏的情况下增加版本。

我认为您不能强制hibernate增加未更改对象的版本号,因为如果没有任何更改(出于明显的原因),它将不会执行任何db
更新
查询


你可以做一个令人讨厌的黑客行为,比如向对象添加一个新字段并手动递增,但就个人而言,这似乎是在浪费时间和资源。我会选择您的显式锁定解决方案,因为它似乎可以满足您的需求,而无需进行不必要的黑客操作。

您可以将更改从子实体传播到父实体。这要求您在修改子实体时传播锁

因此,您需要让所有实体实现一个接口:

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

我刚刚实现了一个类似的东西,它像beast一样快速而漂亮地工作。 在你要救你的“孩子”的时候,打电话做一些类似的事情:

save(child);
T parent = child.getParentEntity();
entityManager.lock(parent, LockModeType.OPTIMISTIC_FORCE_INCREMENT);
您需要访问实体管理器,该管理器可以在spring中获得,如:

  @PersistenceContext
  private EntityManager entityManager;

您的父实体应该具有来自javax.persistence.Version的@Version,而不是spring实体。(我假设在孩子保存时刻,当你保存孩子时,你将完成所有的验证和事情,父母肯定会弄脏)

在这种情况下,你可能想回答你自己的问题。也许有人会来投票给你。或者更好,这可能会帮助正在寻找类似东西的人。如何在spring配置中实现这一点。put(“hibernate.integrator_provider”,(IntegratorProvider)(->Collections.singletonList(rootawareeventlisteneregulator.INSTANCE));
hibernate.integrator\u提供程序
属性也可以采用完全限定的类名,因此您也可以通过Spring提供。只需将逻辑(当前表示为lambda)封装在一个专用类中即可。由于此解决方案在技术上是正确的,并生成正确的db状态,因此hibernate中仍然存在一个bug。(). 乐观锁增量在事务提交时验证。因此,如果您想将正确的版本返回给事务之外的调用方,那么它基本上是无用的。我认为这是web服务的常见情况。这个问题与手动调用flush有关,这是一种代码味道。无论如何,在提交之前都会调用Flush,以呈现预期的结果。但是,Session.Flush和Transaction.commit之间存在差异。使用flush时,只有语句将在DB级别执行,但commit将在DB端触发commit。因此,如果在刷新后执行某些代码并触发异常,则刷新的命令仍将回滚。当事务已经提交时,情况并非如此@Vladmichalcea:您是否知道如何从方法返回正确的版本号,以及如何对所有代码行具有回滚意识?
save(child);
T parent = child.getParentEntity();
entityManager.lock(parent, LockModeType.OPTIMISTIC_FORCE_INCREMENT);
  @PersistenceContext
  private EntityManager entityManager;