How to increment the parent entity version whenever a child entity gets modified with JPA and Hibernate

(Last Updated On: January 29, 2018)


StackOverflow and the Hibernate forum are gold mines. Yesterday, I bumped on the following question on our forum:

Usually, the rationale behind clustering objects together is to form a transactional boundary inside which business invariants are protected. I’ve noticed that with the OPTIMISTIC locking mode changes to a child entity will not cause a version increment on the root. This behavior makes it quite useless to cluster objects together in the first place.

Is there a way to configure Hibernate so that any changes to an object cluster will cause the root object’s version to increment? I’ve read about OPTIMISTIC_FORCE_INCREMENT but I think this does increment the version regardless of if entities were changed or not. Since reads shouldn’t be conflicting with other reads in most scenarios, this doesn’t seem so useful either.

I could always increment the version inside every mutating behavior of the root, but that is quite error-prone. I’ve also thought of perhaps using AOP to do this, but before looking into it, I wanted to know if there were any easy way to do that. If there were a way to check if an object graph is dirty, then it would make it quite easy to implement as well.

What a brilliant question! This post is going to demonstrate how easy you can implement such a requirement when using Hibernate.

Domain Model

First, let’s assume we have the following entities in our system:


The Post is the root entity, and it might have several PostComment entities. Every PostComment can have at most one PostCommentDetails. These entities are mapped as follows:

@Entity(name = "Post") 
@Table(name = "post")
public class Post {

    private Long id;

    private String title;

    private int version;

    //Getters and setters omitted for brevity

@Entity(name = "PostComment")
@Table(name = "post_comment")
public class PostComment 
    implements RootAware<Post> {

    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    private Post post;

    private String review;

    //Getters and setters omitted for brevity

    public Post root() {
        return post;

@Entity(name = "PostCommentDetails")
@Table(name = "post_comment_details")
public class PostCommentDetails 
    implements RootAware<Post> {

    private Long id;

    @OneToOne(fetch = FetchType.LAZY)
    private PostComment comment;

    private int votes;

    //Getters and setters omitted for brevity

    public Post root() {
        return comment.root();

As you probably noticed, the @OneToOne association uses the awesome @MapsId mapping which I already explained in this post.

The PostComment and PostCommentDetails entities are implementing the RootAware interface which is very straightforward:

public interface RootAware<T> {
    T root();

By implementing the RootAware interface, we can resolve the root entity for any PostComment and PostCommentDetails entity.

Event Listeners

Contrary to popular belief, Hibernate is not just an ORM framework but a very customizable data access platform. For our example, we need to intercept any child entity modification and acquire an OPTIMISTIC_FORCE_INCREMENT event on the associated root entity.

To intercept the UPDATE and the DELETE SQL events, the following custom entity event listener is needed:

public static class RootAwareUpdateAndDeleteEventListener 
    implements FlushEntityEventListener {

    private static final Logger LOGGER = 

    public static final RootAwareUpdateAndDeleteEventListener INSTANCE = 
        new RootAwareUpdateAndDeleteEventListener();

    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();
      "Incrementing {} entity version because a {} child entity has been updated", 
                    root, entity);
                incrementRootVersion(event, root);
            else if (deleted(event)) {
                Object root = rootAware.root();
      "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;

This event listener is going to be executed whenever an entity is flushed by the currently running Persistence Context. Every entity modification is automatically detected by the dirty checking mechanism and marked as dirty.

If the entity is dirty and implements the RootAware interface, then we can just lock the parent entity with an OPTIMISTIC_FORCE_INCREMENT lock type. This lock type is going to increment the root entity version during the flush operation.

To intercept when new child entities are being persisted, the following event listener is needed:

public static class RootAwareInsertEventListener 
    implements PersistEventListener {

    private static final Logger LOGGER = 

    public static final RootAwareInsertEventListener INSTANCE = 
        new RootAwareInsertEventListener();

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

  "Incrementing {} entity version because a {} child entity has been inserted", 
                root, entity);

    public void onPersist(PersistEvent event, Map createdAlready) 
        throws HibernateException {

To register these two event listeners, we need to provide a org.hibernate.integrator.spi.Integrator implementation:

public class RootAwareEventListenerIntegrator
    implements org.hibernate.integrator.spi.Integrator {

    public static final RootAwareEventListenerIntegrator INSTANCE = 
        new RootAwareEventListenerIntegrator();

    public void integrate(
            Metadata metadata,
            SessionFactoryImplementor sessionFactory,
            SessionFactoryServiceRegistry serviceRegistry) {

        final EventListenerRegistry eventListenerRegistry =


    public void disintegrate(
            SessionFactoryImplementor sessionFactory,
            SessionFactoryServiceRegistry serviceRegistry) {
        //Do nothing

When bootstrapping the JPA EntityManagerFactory, we can provide the RootAwareEventListenerIntegrator via the hibernate.integrator_provider configuration property:

    (IntegratorProvider) () -> Collections.singletonList(

Testing time

Assuming we have the following entities within our system:

doInJPA(entityManager -> {
    Post post = new Post();
    post.setTitle("High-Performance Java Persistence");

    PostComment comment1 = new PostComment();

    PostCommentDetails details1 = new PostCommentDetails();

    PostComment comment2 = new PostComment();

    PostCommentDetails details2 = new PostCommentDetails();


Updating child entities

When updating a PostCommentDetails entity:

PostCommentDetails postCommentDetails = entityManager.createQuery(
    "select pcd " +
    "from PostCommentDetails pcd " +
    "join fetch pcd.comment pc " +
    "join fetch p " +
    "where = :id", PostCommentDetails.class)
.setParameter("id", 2L)


Hibernate generates the following SQL statements:

SELECT  pcd.comment_id AS comment_2_2_0_ , AS id1_1_1_ , AS id1_0_2_ ,
        pcd.votes AS votes1_2_0_ ,
        pc.post_id AS post_id3_1_1_ , 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 =
INNER JOIN post p ON pc.post_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

As you can see, not only the post_comment_details row gets updated but the post version is also incremented.

The same goes for the PostComment entity modifications:

PostComment postComment = entityManager.createQuery(
    "select pc " +
    "from PostComment pc " +
    "join fetch p " +
    "where = :id", PostComment.class)
.setParameter("id", 2L)


Hibernate generating the following SQL statements:

SELECT AS id1_1_0_ , AS id1_0_1_ ,
        pc.post_id AS post_id3_1_0_ , AS review2_1_0_ ,
        p.title AS title2_0_1_ ,
        p.version AS version3_0_1_
FROM    post_comment pc
INNER JOIN post p ON pc.post_id =

UPDATE post_comment 
SET post_id = 1, review = 'Brilliant!' 
WHERE id = 2

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

Adding new child entities

The parent Post entity version is incremented even when a new child entity is being persisted:

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

PostComment postComment = new PostComment();
postComment.setReview("Worth it!");

Hibernate generating the following SQL statements:

SELECT AS id1_0_0_ ,
       p.title AS title2_0_0_ ,
       p.version AS version3_0_0_
FROM   post p

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

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

Removing child entities

This solution works even when removing existing child entities:

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

Hibernate being able to increment the parent entity version accordingly:

SELECT AS id1_1_0_ ,
       pc.post_id AS post_id3_1_0_ , AS review2_1_0_
FROM   post_comment pc

SELECT AS id1_0_0_ ,
       p.title AS title2_0_0_ ,
       p.version AS version3_0_0_
FROM   post p

DELETE FROM post_comment 
WHERE id = 3

UPDATE post 
SET version = 4 
WHERE id = 1 and version = 3

Cool, right?

If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.


Synchronizing a root entity version for every child entity modification is fairly easy with Hibernate. Hibernate supports many concurrency control mechanisms, as illustrated in this comprehensive tutorial.

Code available on GitHub.

Subscribe to our Newsletter

* indicates required
10 000 readers have found this blog worth following!

If you subscribe to my newsletter, you'll get:
  • A free sample of my Video Course about running Integration tests at warp-speed using Docker and tmpfs
  • 3 chapters from my book, High-Performance Java Persistence, 
  • a 10% discount coupon for my book. 
Get the most out of your persistence layer!


28 thoughts on “How to increment the parent entity version whenever a child entity gets modified with JPA and Hibernate

  1. Hello Vlad,
    I’ve tried this approach and it worked great when updating/deleting child entities. What bothers me is that it fails when I want to delete whole object tree (parent entity and all its children). The listener basicaly wants to increment version on already deleted parent.
    Could you help me with this?

  2. I tested this solution with envers but the root entity is not part of the revision when a child entity is modified (although its version has been increased).

    It would be nice if any change made to any entity belonging to an aggregate would increase that aggregate’s root entity version and would also create an audit record for the root.

    I could then list all revisions for the whole aggregate simply like this: auditReader.getRevisions(Root.class, rootId)

      1. Yes, if a child entity is modified, the version number of the root is increased but no envers audit record is created for the root. So, there is no way to obtain a global list of envers revisions for the whole graph.

        Let’s take the classic scenario with order/order-lines:
        – revision #1 : I create an order (version 1) + 2 order-lines
        – revision #2 : I modify an order-line, my order is now in version 2 (thanks to the listener)
        – revision #3 : I delete an order-line, my order is now in version 3 (thanks to the listener)

        If I retrieve the list of revisions for my root entity via envers, I only get revisions #1 and #3.
        I would have expected to get all 3.

        So the question is: when the root is locked with OPTIMISTIC_FORCE_INCREMENT, is there a way to force envers to create an audit record for the root too?

      2. Well, introducing debezium just for that usecase seems a bit overkill.

        I’d prefer to manually update a dumb last-modification field on my root each time a child is modified so that the root is viewed as modified by envers and is included in the revision along with the child.

      3. Just for one use case, it does not justify to switch to a new CDC framework. However, if you have multiple systems operating with the DB, and some of them are not using Hibernate, then Debezium is a much better option.

  3. But in domain driven design you don’t have the root entity. You have only an id. Bidirectional associations are horrible things. I’ am struggling with all the jee stack. It was thought for anemic model. If i have an order and i’ am changing an order detail can i dirty root entity without inverse association ?

    1. Of course you have a root. That’s what DDD Aggregates are all about.

      DDD is a fine concept as long as you mind the underlying database operations. If you try to stick to OOP principles and ignore what happens in the DB, you are going to have performance issues.

      As explained in my book, unidirectional collections perform badly compared to their bidirectional counterparts.

      To answer your question, you don’t need to navigate to the root entity, you can always use OPTIMISTIC_FORCE_INCREMENT against the root entity reference that you can fetch directly.

      1. In DDD you have root entities (the root of an aggregate), but it is not necessary to put the reference inside other entities, you can create a value object for each identifier for examples ItemId, OrderId (all @Embeddable). I’am using OPTIMISTIC_FORCE_INCREMENT with spring-data-jpa with the @Lock annotation, but this is an error-prone job.

      2. Pro Tip: Unless you are using a document storage, you cannot apply all the DDD principles without affecting the data access patterns. For a RDBMS, it’s better to map the DB to the Domain Model, than the other way around.

  4. Hi, could you give me some real scenario on this example when this updating of root entity is needed. I don’t doubt there exists a good reason to do this I am just not sure if I understand when this is needed and I would like to make sure I understand 🙂 Thanks a lot

    1. If you operate on entity aggregates and you want to make sure that nothing has changed since you loaded the aggregate, then this solution is the perfect fit.

      Imagine a Version Control System, where the Repository entity has a list of Commits, each Commit with a list of Changes. If a new commit was added or amended (e.g. git), then you need to merge the Repository entity to fetch the latest status of the master branch.

Leave a Reply

Your email address will not be published. Required fields are marked *