Hibernate 版本控制父实体

Pri*_*iit 5 java hibernate jpa locking transactions

考虑两个实体 Parent 和 Child。

  • Child 是 Parent 临时集合的一部分
  • Child 有一个 ManyToOne 映射到父 FetchType.LAZY

两者都以相同的形式显示给用户。当用户保存数据时,我们首先更新父实例,然后更新子集合(均使用合并)。

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

我想看到只有 Parent 被版本化的情况,每次我为 Parent 调用合并时,即使在 db 中没有执行实际更新,版本也会始终更新。

Vla*_*cea 5

您可以将更改从子实体传播到父实体。这要求您在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();
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,您需要两个事件侦听器:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

public class RootAwareUpdateAndDeleteEventListener
    implements FlushEntityEventListener {
 
    private static final Logger LOGGER =
        LoggerFactory.getLogger(RootAwareUpdateAndDeleteEventListener.class);
 
    public static final RootAwareUpdateAndDeleteEventListener INSTANCE =
        new RootAwareUpdateAndDeleteEventListener();
 
    @Override
    public void onFlushEntity(FlushEntityEvent event) throws HibernateException {
        final EntityEntry entry = event.getEntityEntry();
        final Object entity = event.getEntity();
        final boolean mightBeDirty = entry.requiresDirtyCheck( entity );
 
        if(mightBeDirty && entity instanceof RootAware) {
            RootAware rootAware = (RootAware) entity;
            if(updated(event)) {
                Object root = rootAware.root();
                LOGGER.info("Incrementing {} entity version because a {} child entity has been updated",
                    root, entity);
                incrementRootVersion(event, root);
            }
            else if (deleted(event)) {
                Object root = rootAware.root();
                LOGGER.info("Incrementing {} entity version because a {} child entity has been deleted",
                    root, entity);
                incrementRootVersion(event, root);
            }
        }
    }
 
    private void incrementRootVersion(FlushEntityEvent event, Object root) {
        event.getSession().lock(root, LockMode.OPTIMISTIC_FORCE_INCREMENT);
    }
 
    private boolean deleted(FlushEntityEvent event) {
        return event.getEntityEntry().getStatus() == Status.DELETED;
    }
 
    private boolean updated(FlushEntityEvent event) {
        final EntityEntry entry = event.getEntityEntry();
        final Object entity = event.getEntity();
 
        int[] dirtyProperties;
        EntityPersister persister = entry.getPersister();
        final Object[] values = event.getPropertyValues();
        SessionImplementor session = event.getSession();
 
        if ( event.hasDatabaseSnapshot() ) {
            dirtyProperties = persister.findModified(
                event.getDatabaseSnapshot(), values, entity, session
            );
        }
        else {
            dirtyProperties = persister.findDirty(
                values, entry.getLoadedState(), entity, session
            );
        }
 
        return dirtyProperties != null;
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以按如下方式注册:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

然后RootAwareFlushEntityEventListenerIntegrator通过 Hibernate 配置属性提供:

configuration.put(
    "hibernate.integrator_provider", 
    (IntegratorProvider) () -> Collections.singletonList(
        RootAwareEventListenerIntegrator.INSTANCE
    )
);
Run Code Online (Sandbox Code Playgroud)

现在,当您修改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);
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)


Pri*_*iit 3

我想我明白了。调用合并后,将返回附加的实例引用。当我使用entityManager.lock(updated, LockModeType.WRITE);获得显式锁定时 那么即使数据库中的父实例未更新,版本号也会增加。

此外,我还将分离实例版本与持久实例版本进行比较。如果它们不匹配,则父级已在数据库中更新,并且版本号也已更改。这可以保持版本号一致。否则,即使合并操作更改了entityManager.lock,也会增加版本号。

仍在寻找解决方案,如何在合并期间实体不脏时使休眠增加版本。