openxava / documentation / Lesson 12: @Calculation and collections total

Course: 1. Getting started | 2. Basic domain model (1) | 3. Basic domain model (2) | 4. Refining the user interface | 5. Agile development6. Mapped superclass inheritance | 7. Entity inheritance | 8. View inheritance | 9. Java properties | 10. Calculated properties | 11. @DefaultValueCalculator in collections | 12. @Calculation and collections totals | 13. @DefaultValueCalculator from file | 14. Manual schema evolution | 15. Multi user default value calculation | 16. Synchronize persistent and computed properties | 17. Logic from database | 18. Advanced validation | 19. Refining the standard behavior | 20. Behavior & business logic | 21. References & collections | A. Architecture & philosophy | B. Java Persistence API | C. Annotations | D. Automated testing

Table of contents

Lesson 12: @Calculation and Collections total
Persistent properties with @Calculation
Total properties of a collection
Summary
We have seen how to calculate amounts using the @DefaultValueCalculator annotation on collections. We will now see how to implement totals using the @Calculation annotation.

If you don't like videos follow the instructions below.

Persistent properties with @Calculation

Sometimes calculated properties are not the best option. Imagine that you have a calculated property in Invoice, let's say discount:
// 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:
business-logic_en025.png
@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).

Total properties of a collection

We want to add amounts to Order and Invoice too. To calculate vat, base amount and total amount are indispensable. To do so you only need to add a few properties to CommercialDocument class. The next figure shows the user interface for these properties:
business-logic_en030.png
Add the next code to CommercialDocument entity:
@Digits(integer=2, fraction=0) // To indicate its size
BigDecimal vatPercentage;
   
@ReadOnly
@Stereotype("MONEY")
@Calculation("sum(details.amount) * vatPercentage / 100")
BigDecimal vat;

@ReadOnly
@Stereotype("MONEY")
@Calculation("sum(details.amount) + vat")    
BigDecimal totalAmount;    
Note how we have chosen @Calculation + @ReadOnly persistent properties over calculated ones for vat and totalAmount, because the calculations are simple, and filtering and ordering for them is very useful. Also, you can see how in @Calculation you can use sum(details.amount) to refer to the sum of the column amount of the collection details, in this way we don't need to have a baseAmount property. On the other hand, vatPercentage is a conventional persistent property. In this case we use @Digits (an annotation from Bean Validation, the validation standard of Java) as an alternative to @Column to specify its size.
Now that you have written the amount properties of CommercialDocument, you must modify the list of properties of the collection to show the total properties of the CommercialDocument (Invoice and Order). Let's see it:
abstract public class CommercialDocument extends Identifiable {
 
    @ElementCollection
    @ListProperties(
        "product.number, product.description, quantity, pricePerUnit, " +
        "amount+[" + 
        	"commercialDocument.vatPercentage," +
        	"commercialDocument.vat," +
        	"commercialDocument.totalAmount" +
        "]" 
    )
    private Collection<Detail> details;
 
    ...
}
Total properties are regular properties of the entity (CommercialDocument in this case) that are placed in the user interface below the column of a collection. For that, in @ListProperties you use square brackets after the property to enumerate them, like amount[commercialDocument.totalAmount]. Moreover, if you want just the summation of the column you don't need a property for that, with a + after the property in @ListProperties is enough, like amount+. In our case we combine both things, + and total properties between [ ].
Now you can try your application. It would behave almost as in figure at the begin of this section. “Almost” because vatPercentage does not have a default value yet. We add it in the next section.

Summary

In this lesson you have learned how to define persistent properties with specific calculations using the @Calculation annotation and we saw the usefulness of the @Digits annotation to define type and length of fields. We also defined total properties using persistent properties with the @ReadOnly +@Calculation annotations, and we saw how to use sum to override a specific property.

Any problem with this lesson? Ask in the forum Everything fine? Go to Lesson 13