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