Sometimes calculated properties are not the best option. Imagine that you have a calculated property in
// DON'T ADD TO YOUR CODE, IT'S JUST TO ILLUSTRATE
public BigDecimal getDiscount() {
return getAmount().multiply(new BigDecimal("0.10"));
}
If you need to process all those invoices with an
discount greater than 1000, you have to code something like the next code:
// DON'T ADD TO YOUR CODE, IT'S JUST TO ILLUSTRATE
Query query = getManager().createQuery("from Invoice"); // No condition in query
for (Object o: query.getResultList()) { // Iterates over all objects
Invoice i = (Invoice) o;
if (i.getDiscount() // Queries every object
.compareTo(new BigDecimal("1000")) > 0) {
i.doSomething();
}
}
You cannot use a condition in the query to discriminate by
discount, because
discount is not in the database, it's only in the Java object, so you have to instantiate every object in order to ask by the
discount. In some cases this way is a good option, but if you have a really huge amount of invoices, and only a few of them have the
discount greater than 1000, then your process will be very inefficient. What alternative do we have?
Our alternative is to use the
@Calculation annotation.
@Calculation is an OpenXava annotation that allows to associate a simple calculation to a persistent property. You can define
discount with
@Calculation as shown the next code:
// DON'T ADD TO YOUR CODE, IT'S JUST TO ILLUSTRATE
@ReadOnly
@Calculation("amount * 0.10")
BigDecimal discount;
This is a regular persistent property, that is with a corresponding column in the database, but it has a calculation defined with
@Calculation. In this case the calculation is
amount * 0.10, so whenever the user changes
amount in the user interface
discount will be recalculated instantly. The recalculated value will be saved in the database when user clicks on
Save, just like in any persistent property. We also have annotated
discount with
@ReadOnly, so it looks and behaves like a calculated property, although you can omit
@ReadOnly so the user could modify the calculated value.
The most useful thing of
@Calculation properties is that you can use it in conditions, so that you can rewrite the above process as shown in the next code:
// DON'T ADD TO YOUR CODE, IT'S JUST TO ILLUSTRATE
Query query = getManager().createQuery("from Invoice i where i.discount > :discount"); // Condition allowed
query.setParameter("discount", new BigDecimal(1000));
for (Object o: query.getResultList()) { // Iterates only over selected objects
Invoice i = (Invoice) o;
i.doSomething();
}
In this way we put the weight of selecting the records on the database server, and not on the Java server. Moreover, the discounts are not recalculated each time, they are already calculated and saved.
This fact also has effect in the list mode, because the user cannot filter or order by calculated properties, but he can do so using persistent properties with
@Calculation:
@Calculation is a good option when you need filtering and sorting, and a simple calculation is enough. A shortcoming of
@Calculation properties is that their values are recalculated only when the user interact with the record and changes some value of the properties used in the calculation, therefore when you add a new
@Calculation property to an entity with existing data you have to update the values of the new column in the table using SQL. On the other hand if you need a complex calculation, with loops or consulting other entities, you still need a calculated property with your Java logic in the getter. In this last case if you need to sort and filter in list mode for the calculated property an option is to have both, the calculated and the persistent property, and synchronize their values using JPA callback methods (we'll talk about callback methods in future lessons).