Configuring @PostLoad and @PostUpdate in JPA/Hibernate

In this post we will discuss how to use the @PostLoad and @PostUpdate annotations in Hibernate/JPA. This tutorial assumes that you have some knowledge of the @PreUpdate and @PrePersist annotations. If you are not familiar with these annotations, then please check our earlier post here.

What are the @PostLoad and @PostUpdate annotations

The @PostLoad and the @PostUpdate annotations are JPA entity callback configuration annotations that are triggered during specific events of entity’s lifecycle.

The @PostLoad annotation callback is triggered once an entity is loaded from the persistence provider. For example, when you search and load an object from a repository. Or when an object is loaded by loading a related entity (more on that later).

The @PostUpdate annotation callback is triggered during a session flush. The callback is triggered only for managed objects which were modified. 

How to use the @PostLoad annotation

In order to configure the @PostLoad annotation, it needs to be added to a method in the entity class. The method should have a void return type and no method arguments.

We will use the same Customer entity from our previous tutorial and configure the @PostLoad annotation there.

@Entity
@Table(name = "customer")
public class Customer {

    private static final Logger log = LoggerFactory.getLogger(Customer.class.getName());

    //some fields here.....

    @PostLoad
    private void postLoadFunction(){
        log.info("Customer PostLoad method called");
    }

Now, let us try it out in a test.

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class PostOperationTest {

    private static final Logger log = LoggerFactory.getLogger(PostOperationTest.class);

    @Autowired
    private CustomerRepository customerRepository;

    @Test
    public void testPostOperations(){

        log.info("Calling search method");
        Optional<Customer> result = customerRepository.findById(73L);
    }
}

When we run this example, we will get the following result.

INFO  com.nullbeans.PostOperationTest          : Calling search method
INFO  c.nullbeans.persistence.models.Customer  : Customer PostLoad method called

As expected, the post load method has been called when the object has been loaded from the database. So what are the potential uses of the @PostLoad annotation? Examples include:

  • Calculating transient values which are not saved in the database.
  • Updating fields that require updates when an object is loaded. For example, if you have a “Last accessed field”.

When is the PostLoad callback triggered

As in our previous example, we saw that the callback was triggered after we did a search using the entity repository and the object was loaded. However, when configuring PostLoad callbacks, one has to be careful and analyse where such callback can be triggered.

This is because the callback can also be triggered if a related object with an eager fetch type is loaded, or if a related object calls the getter method which loads instances of the entity.

For example, let us configure an entity called “BankBranch”, with a one-to-many relationship to our “Customer” entity. The “BankBranch” entity will also have a PostLoad callback configured.

@Entity
@Table(name = "bank_branch" )
public class BankBranch {

    private static final Logger log = LoggerFactory.getLogger(BankBranch.class.getName());

    //some fields here....

    @PostLoad
    private void postLoadFunction(){
        log.info("BankBranch PostLoad method called");
    }

And now, we map the “Customer” side of the relationship.

@Entity
@Table(name = "customer")
public class Customer {

    //some code.....

    private BankBranch branch;

    @ManyToOne
    @JoinColumn(name = "branch_id", referencedColumnName = "id", nullable = false)
    public BankBranch getBranch() {
        return branch;
    }

    public void setBranch(BankBranch branch) {
        this.branch = branch;
    }

    //some code....

Now, let us run our previous test and check the results.

INFO com.nullbeans.PostOperationTest          : Calling search method
INFO c.nullbeans.persistence.models.Customer  : Customer PostLoad method called
INFO c.n.persistence.models.BankBranch        : BankBranch PostLoad method called

Notice that not only the “Customer” PostLoad callback was called but also the “BankBranch”. This shows that when loading an object, a cascade of callbacks can be triggered depending on your configurations. Therefore, we need to be careful when configuring callbacks, especially the PostLoad callback, in order not to suffer from performance issues.

How to use the @PostUpdate annotation

The PostUpdate callback is triggered when a modified managed object is saved into the data provider/database. This usually happens right after a session flush is triggered and a database “Update” statement is triggered.

Just like most other JPA callbacks, the @PostUpdate annotation should be added to an entity method which has a void return type and zero arguments. Let us try it out with the “Customer” entity class.

@Entity
@Table(name = "customer")
public class Customer {

    //some code....

    private static final Logger log = LoggerFactory.getLogger(Customer.class.getName());

    @PostUpdate
    private void postUpdateFunction(){
        log.info("Customer PostUpdate method called");
    }

For illustration purposes in our next test, we will have the @PostLoad, @PreUpdate and @PostUpdate. The complete configuration in the “Customer” entity class is as follows.

@Entity
@Table(name = "customer")
public class Customer {

    private static final Logger log = LoggerFactory.getLogger(Customer.class.getName());

    @PostLoad
    private void postLoadFunction(){
        log.info("Customer PostLoad method called");
    }


    @PreUpdate
    private void preUpdateFunction(){
        log.info("Customer PreUpdate method called");
    }

    @PostUpdate
    private void postUpdateFunction(){
        log.info("Customer PostUpdate method called");
    }

    //some code....

So let us try out our configuration with a test. We will modify our previous test as follows.

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class PostOperationTest {

    private static final Logger log = LoggerFactory.getLogger(PostOperationTest.class);

    @Autowired
    private CustomerRepository customerRepository;
    
    @Autowired
    private EntityManager entityManager;

    @Test
    public void testPrePersist(){

        log.info("Calling search method");
        Optional<Customer> result = customerRepository.findById(73L);

        log.info("Modifying result locally");
        result.get().setName("Modified customer");

        log.info("Marking object for update");
        customerRepository.save(result.get());

        log.info("Flushing session");
        entityManager.flush();
    }

}

Next, we run our example.

INFO  com.nullbeans.PostOperationTest          : Calling search method
INFO  c.nullbeans.persistence.models.Customer  : Customer PostLoad method called
INFO  com.nullbeans.PostOperationTest          : Modifying result locally
INFO  com.nullbeans.PostOperationTest          : Marking object for update
INFO  com.nullbeans.PostOperationTest          : Flushing session
INFO  c.nullbeans.persistence.models.Customer  : Customer Preupdate method called
INFO  c.nullbeans.persistence.models.Customer  : Customer PostUpdate method called

And, let us discuss the results step by step:

  • First, the object has been loaded from the database. This triggered the PostLoad callback for the “Customer entity”.
  • In the next step, we modified the object. Notice that neither the “PreUpdate” nor the “PostUpdate” callbacks were called.
  • Next, we used the repository’s save method. Even though we called it, no callbacks were triggered. Note that you do not need to call the save method when a managed object is modified. It was only done for test purposes.
  • In the last step, we triggered a flush from the entity manager. Notice that first the PreUpdate callback was called, and later the PostUpdate callback was triggered. Please keep in mind that in between the callbacks, a database save operation was performed.

The “PostUpdate” callback allows us to perform operations after an object update has been performed. Depending on your application, this can be useful to perform recalculation of transient values or trigger modifications in related entities.

When is the PostUpdate callback triggered

The PostUpdate callback is triggered after a session flush has been performed. However, if an entity instance was not modified, then no update callbacks will be triggered when the session is flushed.

Let us visualize the lifecycle events by this (very humble) diagram.

This diagram illustrates the order of events of the triggered callback methods. Notice that if the object is not modified, then no Update callbacks are triggered.

For example, if we did not modify the customer name in our test, then neither the “PreUpdate” nor the “PostUpdate” callbacks will be triggered.

        log.info("Calling search method");
        Optional<Customer> result = customerRepository.findById(73L);

        //log.info("Modifying result locally");
        //result.get().setName("Modified customer");

        log.info("Marking object for update");
        customerRepository.save(result.get());

        log.info("Flushing session");
        entityManager.flush();

The results of the modified test will be as follows.

INFO  com.nullbeans.PostOperationTest          : Calling search method
INFO  c.nullbeans.persistence.models.Customer  : Customer PostLoad method called
INFO  com.nullbeans.PostOperationTest          : Marking object for update
INFO  com.nullbeans.PostOperationTest          : Flushing session

Also note that callbacks will not be triggered for entities that are detached at the time of the session flushing.

Summary

In this tutorial, we discussed the @PostLoad and @PostUpdate annotatios and how they are used. We also discussed when these callbacks are triggered and what might be the potential uses for such callbacks.

If you liked this tutorial or if you have any questions, then please let us know in the comments below 🙂