Беда с зимней оптимистичной блокировкой и коллекциями - PullRequest
4 голосов
/ 02 августа 2011

У меня проблемы с использованием оптимистической блокировки. У меня есть постоянный объект с номером версии. Я хочу, чтобы этот номер версии увеличивался только тогда, когда мой постоянный объект «действительно» обновляется, то есть когда одно или несколько полей были изменены или когда в моей сущности была изменена коллекция, помеченная аннотацией @ManyToOne или @OneToMany база данных. Что происходит, так это то, что версия увеличивается только тогда, когда поле, содержащееся непосредственно в моей сущности, изменилось, а нет, когда изменилась коллекция .

Примечание: я добавил select-before-update в мою аннотацию Entity. Не знаю, может ли это изменить поведение версий в коллекциях! У меня также есть поле, которое не должно влиять на версию в моей сущности, в которую я помещаю аннотацию @OptimisticLock(exclude=true).

Кто-нибудь знает, как я могу попытаться заставить работать мои версии? Согласно тому, что я прочитал на нескольких форумах, номер версии должен автоматически увеличиваться при изменении коллекции. Почему бы не быть в моем случае? Есть идеи?

Ответы [ 4 ]

2 голосов
/ 30 августа 2016

Только однонаправленные изменения коллекции будут распространяться на версию родительского объекта , как описано в этой статье . Поскольку вы используете двунаправленную ассоциацию, эта сторона будет контролировать сторону @ManyToOne, поэтому добавление / удаление объекта в коллекции родительской стороны не повлияет на версию родительского объекта.

Однако вы все равно можете распространять изменения от дочерних сущностей к родительским сущностям. Это требует от вас распространять блокировку 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
    }
}

и затем укажите RootAwareFlushEntityEventListenerIntegrator через свойство конфигурации Hibernate:

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 голос
/ 05 июля 2012

После прочтения документации я подумал: 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;
}
0 голосов
/ 11 октября 2012

Выберите перед обновлением, только извлекает состояние экземпляра сущности, а не коллекции и ассоциации. Поэтому, когда вы обновляете коллекцию, а затем обновляете сущность, выбор перед обновлением игнорирует обновление коллекции. Вы можете попытаться использовать операцию слияния, которая загружает объект (включая коллекции) и копирует изменения из отдельного экземпляра и передает вам объект, который является постоянным.

0 голосов
/ 03 августа 2011

Вы должны добавить некоторые параметры (CascadeType) в аннотацию @OneToMany, например, (родительский объект)

@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 (имя домена изменено)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...