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!