2

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?

l2p
  • 420
  • 3
  • 9
  • Foreign key is missing. You should have a foreign-key column on the owner table (History) to create a relationship between the two tables. – Kalyan May 25 '14 at 00:30
  • Merge can manage both the persist and update. If it is a transient instance then it persists the entity and returns the managed instance, if detached then it updates and returns the updated instance. So maybe you are calling merge on a transient instance and so only the prepersist handler is being called. – Shailendra May 25 '14 at 01:42

2 Answers2

1

You need to add a foreign key column in History entity/table to create the relationship between the two tables as below.

@Entity
public class History implements Serializable {

    @Id
    @GeneratedValue
    private long id;

    @Column(name="WINE_ID", updateable=false, insertable=false) 
    private long wineId;

    @Column
    private int oldAmount;

    @Column
    private int newAmount;

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name="WINE_ID", referencedColumnName="ID")
    private Wine wine;

    //getters & setters
}
Kalyan
  • 1,781
  • 11
  • 15
  • Eclipselink already manages the relations between the tables correctly since it persists/updates related entities just fine in normal circumstances. It is just not working when i add the History inside a @PreUpdate method. – l2p May 25 '14 at 10:20
  • I understand. The reason I think why your Wine entity is not getting updated/persisted along with History is because the no foreign key relationship specified in History using `@JoinColumn`. If you don't specify the join relationship, by default any JPA implementation will use primary key which may not be correct all the time. I would suggest you to try once specifying foreign key relationship using `@JoinColumn` as mentioned in the answer. – Kalyan May 25 '14 at 10:35
  • 1
    I tried, it didnt work. And i should clarify i'm updating/persisting a Wine and expect it to cascade to the History. – l2p May 25 '14 at 11:15
1

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.

Chris
  • 20,138
  • 2
  • 29
  • 43