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