I am working on a basic JPA Setup with eclipselink. I have an entity which manages the amount in stock of an item. I want to keep track of the stock history of these items, so I want every update to the amount to also create a new history entry.
The problem is when I add a new History entity to the history List in a @PreUpdate
method it will not be persisted with the Wine entity. Adding the History entity manually before calling merge()
or in a @PrePersist
method works as expected.
Following is my code:
Wine.java
@Entity
public class Wine implements Serializable {
@Id
@GeneratedValue
private long id;
@Transient
private int oldAmount;
@Column
private int amount;
@OneToMany(mappedBy = "wine", cascade = CascadeType.ALL)
private List<AmountHistory> history;
//getters & setters
@PostLoad
@PostUpdate
@PostPersist
private void onLoad() {
oldAmount = amount;
}
@PrePersist
private void onPersist() {
final History history = new History();
history.setOldAmount(0);
history.setNewAmount(amount);
history.setWine(this);
this.history = new ArrayList<>();
this.history.add(history);
}
@PreUpdate
private void onUpdate() {
if (oldAmount != amount) {
final History history = new History();
history.setOldAmount(oldAmount);
history.setNewAmount(amount);
history.setWine(this);
this.history.add(history);
}
}
}
History.java
@Entity
public class History implements Serializable {
@Id
@GeneratedValue
private long id;
@Column
private int oldAmount;
@Column
private int newAmount;
@ManyToOne(cascade = CascadeType.ALL)
private Wine wine;
//getters & setters
}
I currently have a workaround by adding the History entry in the setter method for the amount, but I am not conviced by this solution because it also creates History entities when merge()
is not called.
What do i need to do to fix this problem? Or can somebody explain why this is happening?
From section 3.5 of the JPA spec: "In general, the lifecycle method of a portable application should not invoke EntityManager or Query operations, access other entity instances, or modify relationships within the same persistence context.[43] A lifecycle callback method may modify the non-relationship state of the entity on which it is invoked."
So you should not be attempting to modify your relationships within the preUpdate callback. It does not work as you intend in this case because the preUpdate occurs after the merge has already occured and cascaded over the relationship - JPA does not provide a way to trigger the merge to occur again without manually calling it.
The solution would be to use your accessor methods to track changes as you already are. Instead of tracking oldAmount, also add a new history instance. This way your merge will pick up the relationship as well as the changed amount.