麻烦的hibernate乐观锁和集合

时间:2011-08-02 19:52:09

标签: java hibernate

我在使用乐观锁定方面遇到了麻烦。我有一个持久对象,版本号。我希望只有当我的持久对象“真正”更新时才会增加此版本号,这意味着当一个或多个字段被修改或者在我的实体中使用@ManyToOne@OneToMany注释映射集合时在数据库中被修改。 发生的情况是,只有当我的实体中直接包含的字段发生变化时,版本才会增加,而当集合发生变化时,不会增加

注意:我在实体注释中添加了 select-before-update 。不知道它是否会改变集合版本控制的行为! 我还有一个字段不应该影响我的实体中的版本,我在其上放置了@OptimisticLock(exclude=true)注释。

有谁知道我如何尝试使我的版本工作?根据我在几个论坛上读到的内容,当集合发生变化时,版本号应该会自动增加。为什么不是我的情况呢?有什么想法吗?

4 个答案:

答案 0 :(得分:2)

只有单向集合更改才会传播到父实体版本as explained in this article。因为您正在使用双向关联,所以@ManyToOne方将控制此关联,因此在父方集合中添加/删除实体不会影响父实体版本。

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

This article详细解释了您应该如何实现这样一个用例。

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

同样适用于PostComment实体。

即使你插入一个新的子实体,它仍然有效:

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管理正确地增加父实体:

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

删除子实体时也有效:

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

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

答案 1 :(得分:1)

在我阅读文档之后我想:https://docs.redhat.com/docs/en-US/JBoss_Enterprise_Web_Server/1.0/html/Hibernate_Annotations_Reference_Guide/ch03s04s03s08.html@OptimisticLock(excluded = true)阻止版本递增。因此,在您给出的示例中,如果您将其放在映射集合的字段上,则它将无法正确执行。

我认为好的例子应该是:

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

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

答案 2 :(得分:0)

您应该向@OneToMany注释添加一些参数(CascadeType),例如(母体)

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

在@ManyToOne(子实体)

@ManyToOne
@OptimisticLock(excluded = true)

示例http://shrubbery.homeip.net/c/display/W/Hibernate+JPA+Tips(域名已更改)

答案 3 :(得分:0)

更新前选择仅检索实体实例的状态,而不检索集合和关联。因此,当您更新集合,然后更新实体时,更新前的选择将忽略集合更新。您可以尝试使用加载实体(包括集合)的合并操作,并从分离的实例复制更改,并将您的实体交给持久化。