简体   繁体   English

休眠乐观锁和集合的问题

[英]Trouble with hibernate optimistic lock and collections

I have a trouble in using optimistic locking.我在使用乐观锁定时遇到了麻烦。 I have a persistent object, with a version number.我有一个带有版本号的持久对象。 I want this version number to be increased only when my persistent object is 'really' updated, meaning when one or more fields have been modified or when a collection mapped in my entity with a @ManyToOne or @OneToMany annotation is modified in the database.我希望仅当我的持久对象“真正”更新时才增加此版本号,这意味着当一个或多个字段被修改时,或者当我的实体中映射的集合与@ManyToOne@OneToMany注释在数据库中被修改时。 What happens is that the version is increased only when a field contained directly in my entity has changed, and not when a collection has changed .发生的情况是,仅当直接包含在我的实体中的字段发生更改时才会增加版本,而不是在集合发生更改时增加

Note : I put select-before-update in my Entity annotation.注意:我将select-before-update放在我的实体注释中。 Don't know if it might changed the behaviour of the versioning on the collections!不知道它是否会改变集合上版本控制的行为! I also have a field which should not influence the version in my entity, on which I put the @OptimisticLock(exclude=true) annotation.我还有一个不应影响实体中版本的字段,我在该字段上放置了@OptimisticLock(exclude=true)注释。

Does anyone know how I may try to make my versioning work?有谁知道我可以如何尝试使我的版本控制工作? According to what I've read on several forums, the version number should be automatically increased when a collection changed.根据我在几个论坛上阅读的内容,当集合更改时,版本号应该会自动增加。 Why wouldn't it be the case in my case?为什么我的情况不是这样? Any idea?任何的想法?

Only unidirectional collection changes are going to be propagated to the parent entity version.只有单向集合更改才会传播到父实体版本。 Because you are using a bidirectional association, it's the @ManyToOne side that will control this association, so adding/removing an entity in the parent-side collection is not going to affect the parent entity version.因为您使用的是双向关联, @ManyToOne端将控制此关联,因此在父端集合中添加/删除实体不会影响父实体版本。

However, you can still propagate changes from child entities to parent entities.但是,您仍然可以将更改从子实体传播到父实体。 This requires you to propagate the OPTIMISTIC_FORCE_INCREMENT lock whenever the child entity is modified.这要求您在OPTIMISTIC_FORCE_INCREMENT时传播OPTIMISTIC_FORCE_INCREMENT锁。

In short, you need to have all your entities implementing a RootAware interface:简而言之,您需要让所有实体都实现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();
    }
}

Then, you need two event listeners:然后,您需要两个事件侦听器:

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

and

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

which you can register as follows:您可以按如下方式注册:

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

and then supply the RootAwareFlushEntityEventListenerIntegrator via a Hibernate configuration property:然后通过 Hibernate 配置属性提供RootAwareFlushEntityEventListenerIntegrator

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

Now, when you modify a PostCommentDetails entity:现在,当您修改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);

The parent Post entity version is modified as well: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

The same goes for the PostComment entity as well. PostComment实体也是如此。

And it works even if you insert a new child entity:即使您插入一个新的子实体,它也能工作:

Post post = entityManager.getReference(Post.class, 1L);

PostComment postComment = new PostComment();
postComment.setId(3L);
postComment.setReview("Worth it!");
postComment.setPost(post);
entityManager.persist(postComment);

Hibernate managing to increment the parent entity properly: Hibernate 设法正确地增加父实体:

SELECT p.id AS id1_0_0_ ,
       p.title AS title2_0_0_ ,
       p.version AS version3_0_0_
FROM   post p
WHERE  p.id = 1

INSERT INTO post_comment (post_id, review, id) 
VALUES (1, 'Worth it!', 3)

UPDATE post 
SET version = 3 
WHERE id = 1 AND version = 2

It also works when removing child entities:它也适用于删除子实体:

PostComment postComment = entityManager.getReference(PostComment.class, 3l);
entityManager.remove(postComment);

Hibernate managing to increment the parent entity in this use case as well: Hibernate 在这个用例中也设法增加父实体:

SELECT pc.id AS id1_1_0_ ,
       pc.post_id AS post_id3_1_0_ ,
       pc.review AS review2_1_0_
FROM   post_comment pc
WHERE  pc.id = 3
 
SELECT p.id AS id1_0_0_ ,
       p.title AS title2_0_0_ ,
       p.version AS version3_0_0_
FROM   post p
WHERE  p.id = 1
 
DELETE FROM post_comment 
WHERE id = 3
 
UPDATE post 
SET version = 4 
WHERE id = 1 and version = 3

I thought, after reading documentation as this : https://docs.redhat.com/docs/en-US/JBoss_Enterprise_Web_Server/1.0/html/Hibernate_Annotations_Reference_Guide/ch03s04s03s08.html , that @OptimisticLock(excluded = true) prevent the version to be incremented.我想,在阅读文档后: https : @OptimisticLock(excluded = true)阻止版本为增加了。 So in the example you give, if you put it on the field mapping the collection it would not act correctly.所以在你给出的例子中,如果你把它放在映射集合的字段上,它就不会正确运行。

I think the good exemple should be :我认为好的例子应该是:

@OneToMany(mappedBy = "parent", cascade = {CascadeType.ALL})
public Set<Child> getChildren()
{
    return children;
}

or要么

@OneToMany(mappedBy = "parent", cascade = {CascadeType.ALL})
@OptimisticLock(excluded = false)
public Set<Child> getChildren()
{
    return children;
}

Select before update only retrieves the state of the entity instance and not the collections and associations.更新前选择仅检索实体实例的状态,而不检索集合和关联。 So when you update the collection, and then update the entity, the select before update ignores the collection update.所以当你更新集合,然后更新实体时,更新前的选择会忽略集合更新。 You can try to use merge operation which loads the entity (including collections) and copies the changes from detached instance and hands you the entity which is persistent.您可以尝试使用合并操作来加载实体(包括集合)并从分离的实例中复制更改,然后将持久化的实体交给您。

You should add some parameters (CascadeType) to @OneToMany annotation, eg (parent entity)您应该向@OneToMany 注释添加一些参数(CascadeType),例如(父实体)

@OneToMany(mappedBy = "parent", cascade = {CascadeType.ALL})
@OptimisticLock(excluded = true)
public Set<Child> getChildren()
{
    return children;
}

And in @ManyToOne (child entity)在@ManyToOne(子实体)中

@ManyToOne
@OptimisticLock(excluded = true)

Example http://shrubbery.homeip.net/c/display/W/Hibernate+JPA+Tips (domain name changed)示例http://shrubbery.homeip.net/c/display/W/Hibernate+JPA+Tips (域名变了)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM