7 What We Haven’t Covered

JPA 2 offers other functionalities in addition to the ones we’ve learned so far. Even if we don’t need them for our sample application, it’s advisable to get acquainted with the most important concepts to enable us to implement the corresponding functions in our own development projects later.

7.1 CriteriaQueryas an Alternative to NamedQuery

In the previous sections, we used the concept of NamedQueries to carry out database queries via JPQL. To do this, we defined (as an example) the following NamedQuery in the entity Campaign with the name findAll:

SELECT * FROM Campaign c ORDER BY c.name

Next, the method createNamedQuery of the EntityManager object was used to create a TypedQuery object on which the actual query could later be executed:

TypedQuery<Campaign> query = entityManager.createNamedQuery(Campaign.findAll, Campaign.class);

This approach has the twin disadvantages that the NamedQuery cannot be changed at runtime and that JPQL commands cannot be debugged.  For these reasons, an alternative to NamedQueries, called CriteriaQueries, is available. These have the advantage that they are created in Java at runtime and can therefore also be changed. However, they are also more complex to create and and harder to read than the JPQL query language developed specifically for this purpose.

To enable us to gain a better understanding of things, we will create the same query as in the NamedQuery once more as a CriteriaQuery. As the first step in this process, we require an instance of the CriteriaBuilder. This we can obtain from the EntityManager:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();

A CriteriaQuery object can be created on the CriteriaBuilder by invoking the method createQuery and specifying the type of return value. Since we want a Campaign object as the return value, the expression will look as follows:

CriteriaQuery<Campaign> criteria = cb.createQuery(Campaign.class);

Further to this, we will create a Root object that corresponds to the FROM clause in JPQL:

Root<Campaign> campaign = criteria.from(Campaign.class);

The query can then be fully configured. In our example, we will select all Campaign objects that should be sorted by the attribute name.

criteria.select(campaign).orderBy(cb.asc(campaign.get("name")));

The CriteriaQuery object is now fully defined and corresponds to the previously used NamedQuery in terms of its content. We can now use the same method to create a TypedQuery object using the EntityManager. This time, however, we will invoke the method createQuery:

TypedQuery<Campaign> query = entityManager.createQuery(criteria);

If we now invoke the method getResultList on this TypedQuery object, the query will be executed on the database. Thus, this step is the same as with a NamedQuery.

7.2 Avoid a LazyInitialisationException Using FetchType

In section 5.4, we suffered a fair amount of frustration over the LazyInitialisationException. The cause of this was that JPA could only load the Donation objects assigned to a campaign from the database by accessing the attribute donations. In JPA, this behaviour is known as a lazy fetch. Alongside the lazy fetch, there also exists the possibility of an eager fetch. In this case, the assigned objects are loaded from the database at the time the higher-level entity is loaded. If we access the assigning attribute later, database access will no longer be required, since the assigned objects will already have been loaded.

For each relationship in JPA, we can determine which strategy should be used for the loading of data. To do this, we must assign a value of the enumeration type FetchType to the fetch attribute of the relevant relationship. This can have two values: EAGER (for eager fetch) and LAZY (for lazy fetch). If no value is specified, JPA uses lazy fetch by default, which causes our LazyInitialisationException to appear. To set this to eager fetch, we must adjust the annotation of the attribute donations in the class Campaign. From the code:

@OneToMany(mappedBy = "campaign")
private List<Donation> donations;

now becomes:

@OneToMany(mappedBy = "campaign", fetch = FetchType.EAGER)
private List<Donation> donations;

At this point, you’re bound to be wondering why we didn’t do this straight away. The reason is that it can be very risky to activate eager fetch for a relationship. Ultimately, it results in many more objects being loaded from the database – including objects that may not be needed for the current use case. If we begin recklessly setting every relationship to eager fetch, it can occur that in extreme cases, accessing the database once can cause all data to be loaded. With a high-performance web application of the type we want to develop, this is obviously not a viable situation.

If we activate the eager fetch in a relationship, it will be used globally for all of the use cases that process entities involved in the relationship.

As a rule of thumb, it’s therefore advisable to activate the eager fetch only when it is required by all affected use cases. In our sample application, this is not the case, since the use case Display and edit campaigns (Cloud Tutorial Java EE 7 in a Week chapter II.4.2) does not require the list of Donation objects. For this reason, it would be a waste of resources to load the list here, and we should set the definition of the relationship back to lazy fetch.

7.3 Object Locking

In many applications, it can occur that multiple users are working on the same object at the same time. To avoid inconsistenies, the object must therefore be locked for access by a particular user. JPA offers optimistic locking and pessimistic locking, for this ourpose, and we’ll deal with both in this section.

As it happens, in our sample application, locking is not required, since a user with the role of organizer is the only permitted to edit their own objects and the user with the role of donor is permitted only to create new objects, not to edit them.

7.3.1 Optimistic Locking

Optimism is always a good thing. An optimist works on the premise that nothing will go wrong, and solves any problems after they arise. A similar principle applies with optimistic locking: we simply assume that concurrent access virtually never occurs, and when it does, it is treated as a special case.

This means that in optimistic locking does not actually result in any objects being locked. But what happens in the special cases where concurrent access does occur?

To deal with these cases, each entity contains a special attribute in which the version of the object is stored. This attribute is annotated with the @Version annotation from the packaage javax.persistence.

In each transaction, this attribute is incremented automatically by JPA; it is also compared with the current status in the database before the transaction begins. If the version of the object in the database is more current than the one in the transaction taking place, another transaction must update the object in advance. Because of this, the transaction will fail and an OptimisticLockException will be thrown.

This has the result that the transaction, which first performs a commit, makes the change in the database. This is carried out on a “first come, first served” basis. Later transactions are simply unlucky. These transactions fail, and the application must load the current status of database in order to re-process it.

In the simplest scenario, the application can leave it to the user to incorporate the changes into the new version of the object. A more user-friendly application might provide its users with a Merge operation to support the modification process.

Incidentally, this is the same behaviour we are familiar with from our source code version control programs (e.g. SVN or GIT): The first user to incorporate the changes of the source code is lucky, and their changes will be accepted in the repository. The next user, however, must use the Merge operation of the version control program and incorporate their intended changes into the current code manually.

7.3.2 Pessimistic Locking

With pessimistic locking, we work on the assumption that concurrent access is highly probable; thus, the affected object is locked for the entire duration in advance. If another transaction tries to access a locked object, it will fail to do so.

The EntityManager offers the method lock for pessimistic locking. This contains the object to be locked as the first parameter and the lock mode as the second. JPA supports two different lock modes:

  • PESSIMISTIC_READ for a read lock
  • PESSIMISTIC_WRITE for a write lock

The following statement, for example, performs a write lock for the object campaign in the current transaction:

entityManager.lock(campaign, LockModeType.PESSIMISTIC_WRITE);

A write lock is an exclusive lock for the current transaction. Since the transaction receives exclusive access to the object, it can also perform changes (in this case, write operations) on this object. If another transaction tries to change or reserve the same object using a write or read lock during the runtime of the locking transaction, the second transaction will be aborted by an exception.

When we use a read lock, we do not wish to change the locked object – we merely want to ensure that the object does not change during the runtime of the transaction. While the values of the object should not be changed, it can be read as many times as required. Thus, we can apply a read lock to multiple transactions by specifying the lock mode PESSIMISTIC_READ. A read lock request only fails when a write lock has already been placed on another transaction. Naturally, write operations also fail when performed on an object that contains a read lock, since the value of the object being read would be subject to change.

7.4 Lifecycle Methods During State Changes

As we have already learned, we can use the EntityManager to change the state of entities. This can be accomplished via either CRUD operations or JPQL statements. In general, changes made to an entity can be classified as one of four operations:

  • Persisting an entity
  • Loading an entity from the database
  • Updating an entity
  • Removing an entity

A developer can define special methods to be invoked by JPA before or after operations. In both cases, the methods are triggered by a change of state that initiates a new phase in the lifecycle of the entity. Because of this, we call them “lifecycle methods”.

If so required, these lifecycle methods can be defined in the class of an entity. In this situation, the method must be a parameterless method with no return value. It will be invoked at different times depending on the annotation added to the method As is always the case for JPA, the annotations are located in the package javax.persistence.

Tab. 7-1 shows the list of annotations for lifecycle methods and the associated event that determines when the annotated method is invoked.

Annotation Event
@PrePersist When the EntityManager determines that a new entity must be created
@PostPersist After the new entity has been persisted in the database
@PostLoad After an entity has been loaded from the database, e.g. with EntityManager.find
@PreUpdate When the EntityManager determines that an entity must be updated
@PostUpdate After an entity has been updated in the database
@PreRemove When the EntityManager determines that an entity must be removed
@PostRemove After an entity has been removed from the database

Tab. 7-1 Annotations for lifecycle methods in JPA

These lifecycle methods are well suited to outputting log messages that record a state change in the entity.

Lifecycle methods whose annotations begin with the prefix @Pre can also be used to store the time stamp of the event in the database. The following method records the date when the change in the entity occurred:

@PreUpdate
void onPreUpdate() {
    changedDate = Calendar.getInstance().getTime();
}

Of course, this requires the entity to possess a changedDate attribute in which the date of change can be stored.

Further to this, the user may desire that the date of change is recorded for more than one entity. In this case, implementing the same lifecycle method in all entities would be redundant. Lifecycle methods can thus be compiled in their own class, a so-called EntityListener. To enable the lifecycle methods to know which entity is currently being changed, each function receives the entity as a parameter. Since the type of the entity is not known at compile time, the superclass Objekt is used. As an example, we will specify the above lifecycle method as a reusable EntityListener:

public class DateUpdateEntityListener {
    @PreUpdate
    void onPreUpdate(Object entity) {
        DateEntity dateEntity = (DateEntity) entity;
        dateEntity.changedDate = Calendar.getInstance().getTime();
    }
}

As you can see, this EntityListener requires that the entity using it is inherited from the type DateEntity, which defines the attribute changedDate[1].

To use the EntityListener, we must annotate the entity with @EntityListeners from the package javax.persistence. The annotation contains the class of the EntityListener as a parameter. In our case:

@Entity
@EntityListeners(DateUpdateEntityListener.class)
public class Something extends DateEntity{...

This annotation causes the changedDate instance variable of the fictitious entity Something to be updated with every change.

Discussion

Use the message board below to give the authors your feedback or to discuss this page’s topic with other readers (in English please!). Please don’t expect the authors to answer directly, but they might update the content of this site according to your feedback.


  1. To keep the example as simple as possible, we will not check the type casting in this case.