In the previous lesson, we learned how to modify our database schema manually in order to modify the data without having to drop the tables. Now we will see how to define default calculations to apply in multi-user environments.
If you don't like videos follow the instructions below.
JPA callback methods
Another useful way to add business logic to your model is using JPA callback methods. A callback method is a method in your entity that is called in some specific moment of its life cycle as a persistent object. That is, you can specify some logic to execute on save, read, remove or modification of the entity.
In this section we'll see some practical applications of JPA callback methods.
Multiuser safe default value calculation
Until now we were calculating the
Invoice and
Order number using
@DefaultValueCalculator. This calculates the default value when the user clicks to create a new
Invoice or
Order. So, if several users click on the
New button at the same time all of them get the same number. This is not multiuser safe. The way to generate a unique number is by generating it just on save.
We are going to implement it using a JPA callback method. JPA allows you to mark any method of your class to be executed in any part of its life cycle. We'll indicate the calculation of the number just before the saving of the
CommercialDocument. Using this approach we'll improve the number calculation for having a different numeration for
Order and
Invoice.
Edit the
CommercialDocument entity and add the
calculateNumber() method:
@PrePersist // Executed just before saving the object for the first time
private void calculateNumber() throws Exception {
Query query = XPersistence.getManager()
.createQuery("select max(i.number) from " +
getClass().getSimpleName() + // Thus it's valid for both Invoice and Order
" i where i.year = :year");
query.setParameter("year", year);
Integer lastNumber = (Integer) query.getSingleResult();
this.number = lastNumber == null ? 1 : lastNumber + 1;
}
This code is the same as that of the
NextNumberForYearCalculator but using
getClass().getSimpleName() instead of “CommercialDocument”. The
getSimpleName() method returns the name of the class without the package, i.e., just the entity name. It will be “Order” for
Order and “Invoice” for
Invoice. Thus we can get a different numeration for
Order and
Invoice.
JPA specification states that you should not use JPA API inside a JPA callback method. So the above method is not legal from a strict JPA viewpoint. But, Hibernate (the JPA implementation OpenXava uses by default) allows you to use it in
@PrePersist. And since JPA is the easier way to do this calculation we use it in our practice.
Now you can delete the
NextNumberForYearCalculator class from your project, and modify the number property of
CommercialDocument to avoid using it:
@Column(length=6)
// @DefaultValueCalculator(value=NextNumberForYearCalculator.class, // Remove this
// properties=@PropertyValue(name="year")
// )
@ReadOnly // The user cannot modify the value
int number;
Note that in addition to removing
@DefaultValueCalculator, we have added the
@ReadOnly annotation. This means that the user cannot enter or modify the
number. This is the right approach given that the number is generated on saving the object, so the user typed value would always be overridden.
Summary
In this lesson we saw how to define properties and values applicable by default, something very useful if our application will be used in multi-user or massive access environments. In future lessons we will see other ways to add business logic, and how to synchronize properties.