How to increment the parent entity version whenever a child entity gets modified

Hi there!

Background
We’re using DDD and Aggregates — e.g., clusters of objects where the Aggregate Root is the root of the cluster. Each Aggregate Root can contain several entities, but the Aggregate Root is the transactional boundary, which means that all entities it contains should be consistent. Only the Aggregate Root can update entities that are part of it.

Problem
In our domain model, the parent/root entity can contain other entities using unidirectional references. We can update Child entities only through the Parent because they should be consistent with each other. Now we want to introduce optimistic locking. Since the Parent entity is the transactional boundary, the lock should be at its level, not at the Child entity level. So if we implement optimistic locking using a version field, when we update a Child, the Parent’s version should be updated. However, this only works if we change the children collection itself, e.g., add or remove a Child. If we modify a Child, it doesn’t work.

Example:

@Entity
public class Parent {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String name;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "parent_id")
    private List<Child> children = new ArrayList<>();

    @Version
    private Long version;

    // works fine - updates Parent's version
    public void add(Child child) {
        children.add(child);
    }

    // doesn't update Parent's version
    public void update(Integer childId) {
        children.stream()
                .filter(x -> x.getId().equals(childId))
                .findFirst()
                .ifPresent(x -> x.setName(UUID.randomUUID().toString()));
    }
}

@Entity
public class Child {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String name;

    // will be called only from the parent
    protected void setName(String name) {
        this.name = name;
    }
}

Possible solution
After some research, I found only this discussion: Hibernate Community • View topic - Increment aggregate root version when child entity modified and this article by Vlad Mihalcea(@vlad): How to increment the parent entity version whenever a child entity gets modified with JPA and Hibernate - Vlad Mihalcea. This is similar to what we’re looking for, except that we have unidirectional dependencies instead of bidirectional. Potentially, we can modify RootAware to store the parent’s ID instead of a direct reference, and then get the Parent from the session:

public interface RootAware<T> {
    Integer getParentId();

    Class<T> getParentClass();
}

@Entity
public class Child implements RootAware<Parent> {
    ...
    
    @Column(name = "parent_id")
    private Integer parentId;

    @Override
    public Integer getParentId() {
        return parentId;
    }

    @Override
    public Class<Parent> getParentClass() {
        return Parent.class;
    }
  
    ...
}

public class RootAwareUpdateAndDeleteEventListener implements FlushEntityEventListener {
    
    @Override
    public void onFlushEntity(FlushEntityEvent event) throws HibernateException {
        Object entity = event.getEntity();
        boolean mightBeDirty = event.getEntityEntry().requiresDirtyCheck(entity);

        if (mightBeDirty && entity instanceof RootAware) {
            RootAware rootAware = (RootAware) entity;
            if (updated(event)) {
                Object root = event.getSession().get(rootAware.getParentClass(), rootAware.getParentId());
                incrementRootVersion(event, root);
            }
        }
    }
    ...
}

Question
Is there any better way to solve this problem? Since Vlad’s article and the discussion are quite old, I was hoping that maybe there are some new features in Hibernate that would allow us to implement this out of the box, or at least in an easier way? (For example, getting all necessary info about the parent from the context without introducing the RootAware interface).

Thanks in advance!

To answer briefly no, there is no native way to tell Hibernate that the parent version should be increased when updating an associated entity. The only way to do this is to trigger an update on the parent by intercepting the event on the children and triggering a version update.

If you mapped your association as an @EntityCollection, then it would be part of the Parent entity’s state and updates to it would also mean a change in the owning entity, thus increasing the version. But that is not the case.

You can map the association as bi-directional, i.e. creating a @ManyToOne con the Child entity side whith the same join column and adding mappedBy to the Parent side. This would change the side that determines how to persist the association, but you always need to maintain both sides of the relationship anyway so it wouldn’t impact your application too much I think.

1 Like

Hi @mbladel !
Thanks for the quick reply.
By:

You can map the association as bi-directional, i.e. creating a @ManyToOne con the Child entity side whith the same join column and adding mappedBy to the Parent side.

Do you mean smth like this?

@Entity
public class Parent {

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "parent")
    private List<Child> children = new ArrayList<>();
}

@Entity
public class Child {

    @ManyToOne
    @JoinColumn(name = "parent_id")
    Parent parent;
}

Because I’ve tried it and it didn’t work. Am I missing something?

Yes, that’s what I meant.

What didn’t work of this mapping? Remember, the side determining the state of the association is the one without mappedBy, so in this case the Child entity. When you add a child instance to the children collection, you should always set the parent property accordingly in it.

It’s the same case that I described in the beginning:

@Entity
public class Parent {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String name;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "parent")
    private List<Child> children = new ArrayList<>();

    @Version
    private Long version;

    // works fine - updates Parent's version
    public void add(Child child) {
        children.add(child);
    }

    // doesn't update Parent's version
    public void update(Integer childId) {
        children.stream()
                .filter(x -> x.getId().equals(childId))
                .findFirst()
                .ifPresent(x -> x.setName(UUID.randomUUID().toString()));
    }
}

@Entity
public class Child {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String name;

    @ManyToOne
    @JoinColumn(name = "parent_id")
    Parent parent;

    // will be called only from the parent
    protected void setName(String name) {
        this.name = name;
    }
}

When I call parent.add(child), it works fine, e.g., it adds the child and updates the parent’s version. However, when I call the method parent.update(childId) to update the Child, it updates the child but doesn’t change the parent’s version.

Having the parent reference on the child side simply makes it easier to force the version upgrade; you will still need to trigger this forced increment. You can easily do this with e.g. an entity lifecycle callback method for @PostUpdate on the Child entity that calls lock with OPTIMISTIC_FORCE_INCREMENT on the to-one parent instance.

Oh, ok, got it, then I just misunderstood you. Thanks!
btw why exactly @PostUpdate and not @PreUpdate?

The post-update method is more correct since it’s only called once the update operation has completed successfully, whereas the pre-update might increment the version on the parent even if the update on the child would fail on the database side.

1 Like

I’ll mark this topic as resolved. Thanks for the help @mbladel!

For anyone checking this topic and needing a solution for a unidirectional association, here is how it can be potentially implemented:

    public interface AggregateMember<ROOT_PK, T extends AggregateRoot<ROOT_PK>> {
        ROOT_PK getRootId();
    }
    
    @MappedSuperclass
    public abstract class AggregateRoot<PK> {
    
        @Version
        private Long version;
    }
    
    @Entity
    public class Parent extends AggregateRoot<Integer> {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Integer id;
    
        @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
        @JoinColumn(name = "parent_id")
        private List<Child> children = new ArrayList<>();
    
        ...
    }
    
    @Entity
    public class Child implements AggregateMember<Integer, Parent> {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Integer id;
    
        private Integer parentId;
    
        public Integer getRootId() {
            return parentId;
        }
        ...
    }
    
    // Or, you can use @PostUpdate and get the Parent entity from EntityManager:
    public class PostUpdateListener implements PostUpdateEventListener {
    
        @Override
        public void onPostUpdate(PostUpdateEvent event) {
            var entity = event.getEntity();
    
            if (entity instanceof AggregateMember<?, ?>) {
    
                var aggregateMember = (AggregateMember<?, ?>) entity;
                // Since we can update the child only through the Root, it should always exist in the session
                var root = event.getSession().get(resolveClass(entity), aggregateMember.getParentId());
    
                event.getSession().lock(root, LockMode.OPTIMISTIC_FORCE_INCREMENT);
            }
        }
    
        ...
    }

Unfortunately, we still need to have parentId in the Child entity, but at least in our case, this is acceptable and preferable compared to a bidirectional association.