openxava / documentation / Lesson 16: Synchronize persistent and computed properties

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 16: Synchronize persistent and computed properties
Synchronizing persistent and calculated properties
Summary
In the previous lesson we learned how to define default properties for development in multi-user environments, using JPA callbacks. We will now see how to synchronize both computed and persistent properties.

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

Synchronizing persistent and calculated properties

As we already have learned, calculated properties do not allow to filter or ordering in list, so we prefer persistent properties with @Calculation. However, @Calculation properties are only valid for simple arithmetic calculations. When you need loops, condition, read from database, connect to external services, or some complex logic, @Calculation is not enough. For these cases you need to write the logic with Java, in the getter. But, how we can do this and at the same time keep the ordering and filtering in list? Simple, you can use two properties, one calculated and one persistent, and synchronize both using JPA callback methods. You're going to learn how to do it in this section.
Let's add a new property to Order entity called estimatedDeliveryDays:
@Depends("date")
public int getEstimatedDeliveryDays() {
    if (getDate().getDayOfYear() < 15) {
        return 20 - getDate().getDayOfYear(); 
    }
    if (getDate().getDayOfWeek() == DayOfWeek.SUNDAY) return 2;
    if (getDate().getDayOfWeek() == DayOfWeek.SATURDAY) return 3;
    return 1;
}
This is a pure calculated property, a getter with Java logic. It calculates the estimated delivery days using date as source. This case cannot be solved with @Calculation that only supports basic arithmetic calculations.
We also have to add estimatedDeliveryDays to the default @View declaration in Order code:
@View(extendsView="super.DEFAULT", 
    members=
        "estimatedDeliveryDays," + // ADD THIS LINE
        "invoice { invoice }"
)
...
public class Order extends CommercialDocument {
The result is this:
business-logic_en050.png
The value is recalculated each time the date changes in the user interface thanks to the @Depends("date") in estimatedDeliveryDays. This is all very nice, but when you go to list mode you cannot order or filter by estimated delivery days. To solve this problem we add a second property, this time a persistent one. Add the next code to your Order entity:
@Column(columnDefinition="INTEGER DEFAULT 1")
int deliveryDays;
Note as we have used @Column(columnDefinition="INTEGER DEFAULT 1"), with this trick when OpenXava creates the column uses "INTEGER DEFAULT 1" as column definition, thus the new column has 1 as default value instead of null, and we avoid an ugly error with our int property. Yes, in many cases @Column(columnDefinition=) is an alternative to do an UPDATE over the table (as we did in "Manual schema evolution" section), although has the problem that is database dependent. Anyways, this columnDefinition dissertation is tangential to our calculated/persistent synchronization issue, @Column is not required at all, it's just convenient for this int property.
This new deliveryDays property will contain the same value as estimatedDeliveryDays, but deliveryDays will be persistent with its corresponding column in the database. The tricky issue here is to have the deliveryDays property synchronized. We will use the JPA callback methods in Order to achieve this. It's enough to assign the value of estimatedDeliveryDays to deliveryDays each time that a new Order is created (@PrePersist) or updated (@PreUpdate).
Add a new recalculateDeliveryDays() method to Order entity annotated with @PrePersist and @PreUpdate, thus:
@PrePersist @PreUpdate 
private void recalculateDeliveryDays() {
    setDeliveryDays(getEstimatedDeliveryDays());
}
Basically, the recalculateDeliveryDays() method is called every time an Order entity is registered in the database for the first time and when the order is updated.
You can try the Order module with this code, and you will see how when a order is created or modified, the column in the database for deliveryDays is correctly updated after saving, ready to be used in massive processing and available for ordering and filter in list.

Summary

In this lesson you have learned how easy it is to use JPA callback methods to define logic at different specific moments in the life cycle of an entity, allowing us to synchronize persistent and calculated properties when modifying an existing entity, or saving an entity for the first time.

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