In my OpenXava application I had an entity with an @OneToMany collection of entities to create a master-detail structure. The main entity, Invoice, has a total persistent property that I want to update everytime that the user adds, removes or changes a detail. The user interface generated by OpenXava is this:
The total is on the footer of the amount column and it is updated when a line is added, modified, or removed. I achieved this effect using JPA callback methods, specifically @PostPersist, @PostUpdate and @PostRemove in the detail class.
This is the code for my Invoice entity:
@Entity @Getter @Setter
@View(members=
"year, number, date;" +
"customer;" +
"details;"
)
@Tab(properties="year, number, date, customer.name, total")
public class Invoice extends Identifiable {
@DefaultValueCalculator(CurrentYearCalculator.class)
@Column(length=4) @Required
int year;
@Column(length=6) @Required
int number;
@Required @DefaultValueCalculator(CurrentDateCalculator.class)
Date date;
@ManyToOne(optional=false, fetch=FetchType.LAZY)
Customer customer;
@Stereotype("MONEY")
BigDecimal total;
@OneToMany(mappedBy="invoice", cascade = CascadeType.ALL)
@ListProperties("product.number, product.description, unitPrice, quantity, amount[invoice.total]")
List<InvoiceDetail> details;
public void recalculateTotal() {
BigDecimal sum = BigDecimal.ZERO;
for (InvoiceDetail detail: details) {
sum = sum.add(detail.getAmount());
}
total = sum;
}
}
And this for the InvoiceDetail entity:
@Entity @Getter @Setter
public class InvoiceDetail extends Identifiable {
@ManyToOne
Invoice invoice;
@ManyToOne(optional=false, fetch=FetchType.LAZY)
Product product;
@Required
BigDecimal unitPrice;
@Required
int quantity;
@Depends("unitPrice, quantity")
public BigDecimal getAmount() {
return new BigDecimal(getQuantity()).multiply(getUnitPrice());
}
@PostPersist @PostUpdate @PostRemove
private void recalculateInvoiceTotal() {
invoice.recalculateTotal();
}
}
The above code worked nicely. However, I refactored it in order to use @ElementCollection instead of @OneToMany collection, so OpenXava will generate a UI where the user can edit the details inline, in this way:
For that I changed the collection definition in Invoice entity to this:
@ElementCollection
@ListProperties("product.number, product.description, unitPrice, quantity, amount[invoice.total]")
List<InvoiceDetail> details;
And I refactored InvoiceDetail as an @Embeddable:
@Embeddable @Getter @Setter
public class InvoiceDetail {
@ManyToOne(optional=false, fetch=FetchType.LAZY)
Product product;
@Required
BigDecimal unitPrice;
@Required
int quantity;
@Depends("unitPrice, quantity")
public BigDecimal getAmount() {
return new BigDecimal(getQuantity()).multiply(getUnitPrice());
}
@PostPersist @PostUpdate @PostRemove
private void recalculateInvoiceTotal() {
// invoice.recalculateTotal(); I no longer can access to invoice
System.out.println("[InvoiceDetail.recalculateInvoiceTotal()] "); // NEVER PRINTED
}
}
The first problem is that I have not access to invoice from InvoiceDetail, but still worse the recalculateInvoiceTotal() method is not executed, never. That is JPA callback methods are not executed in the @Embeddable for @ElementCollection.
Can JPA callback method be executed in @Embeddable? Is there a way to solve this case?