Hibernate versioning parent entity

前端 未结 4 1284
暖寄归人
暖寄归人 2021-01-13 17:51

Consider two entities Parent and Child.

  • Child is part of Parent\'s transient collection
  • Child has a ManyToOne mapping to parent with FetchType.LAZY
4条回答
  •  逝去的感伤
    2021-01-13 18:33

    You can propagate changes from child entities to parent entities. This requires you to propagate the OPTIMISTIC_FORCE_INCREMENT lock whenever the child entity is modified.

    So, you need to have all your entities implementing a RootAware interface:

    public interface RootAware {
        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 {
     
        @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 {
     
        @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:

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

    Now, when you modify a PostCommentDetails entity:

    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:

    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
    

提交回复
热议问题