I have two entities with relation one to many. Parent can have several Child entity instances. I added a field to the parent that stores the date of children modifications(childrenLastModifiedDate). To maintain that, I added method:
@PrePersist
@PreUpdate
@PreRemove
private void handle() {
parent.setChildrenLastModifiedDate(now());
}
Here is the problem. It's not always invoke when the child is saved. Locally(mac os), it works as expected, all three types of changes are tracked and saved to the parent entity. However, on the server(linux) it only works for:
Even though, PreUpdate is invoked, the changes are not saved. I have even tried to add a direct repository call to save the parent. The result is the same. Nothing is saved on update, but saved on remove or persist. I tried to make this change in additional transaction and it worked, but it's too resource consuming. Also, I can see that someone had very similar experience:
JPA/Hibernate preUpdate doesn't update parent object
However, there is nothing on how to handle the problem itself. The question is: is there a way to guarantee that using of this annotations will always work and perform additional updates of dependent entities? If it's not, what is the best way to handle such logic? The update is required on each change to children. Even if it is saved by cascade.
I got the same problem. I fixed it by using Hibernate Interceptor.
First, you have to declare some base method in the base entity class that allow us to find the parent of an entity, an a method to chaining update your desired fields.
public abstract class BaseEntity {
public Optional<BaseEntity> getParent() {
// subclass should override this method to return the parent entity
return Optional.empty();
}
public void updateChain() {
// update your desired fields
...
this.getParent().ifPresent(p -> p.updateChain());
}
}
Then you can use following Hibernate interceptor to force update all parents of the dirty entities.
public class EntityChainForceUpdateHibernateInterceptor extends EmptyInterceptor {
@Override
public void preFlush(Iterator entities) {
entities.forEachRemaining(e -> {
if (BaseEntity.class.isAssignableFrom(e.getClass())) {
BaseEntity b = (BaseEntity) e;
b.getParent().ifPresent(p -> p.updateChain());
}
});
}
}
To register this interceptor with Spring, just add following line to application.properties
spring.jpa.properties.hibernate.ejb.interceptor=com.your.package.EntityChainForceUpdateHibernateInterceptor
And this is an example of how to override the getParent()
.
@MappedSuperclass
public abstract class BaseEntity {
@Column(name = "last_updated_at")
private Instant lastUpdatedAt;
public Optional<BaseEntity> getParent() {
return Optional.empty();
}
public void updateChain() {
this.lastUpdatedAt = Instant.now();
this.getParent().ifPresent(p -> p.updateChain());
}
}
@Entity
public class ParentEntity extends BaseEntity {
@Id
@Column(name = "id")
private String id;
@Column(name = "something")
private String somethingToUpdateWhenChildSaved;
@Override
public void updateChain() {
this.somethingToUpdateWhenChildSaved = "anything";
super.updateChain();
}
}
@Entity
public class ChildEntity extends BaseEntity {
@ManyToOne
@JoinColumn(name = "parent_id", referencedColumnName = "id")
private ParentEntity parent;
@Override
public Optional<BaseEntity> getParent() {
return Optional.of(this.parent);
}
}
It seems your issue is with @PreUpdate
only.
My personal opinion is your entity was not updated if @PreUpdate was not called. But you can check it easily. I would recommend use optimistic lock (just use version field following hibernate doc) or auditing
for entities where you need check update
activity. If update was processed you could definitely detect if @PreUpdate
activity processed or not. After some tests you'll detect where was the issue.
I've never checked behavior if @PreUpdate
is used at same time for the entity and for subscribed @EntityListeners
for the entity. As for me conflict could be in this case. Not sure but if you have both of this annotations, please check complex and separate behavior (Who knows, may be hibernate developers decided that impossible case, because you do not need @PreUpdate
handlers in entity if some declared in your entity listener implementation. If you'll check it, please let me know in comment).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With