Search code examples
javajpaeclipselink

EntityManager.merge() not persisting related entities


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?


Solution

  • 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.