1. Overview
It’s common to have a scenario where we need to change a field value before persisting the entity to the database when working with Hibernate. Such scenarios could arise from user requirements to perform necessary field transformations.
In this tutorial, let’s take a simple example use case, which converts a field value to uppercase in nature before performing an update and insert. We’ll also see the different approaches to achieve this.
2. Entity Lifecycle Callbacks
First of all, let’s define a simple entity class Student for our illustration:
@Entity
@Table(name = "student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column
private String name;
// getters and setters
}
The first approach we’re going to review is JPA entity lifecycle callbacks. JPA provides a set of annotations that allow us to execute a method at different JPA lifecycle events such as:
- @PrePresist — Execute before an insert event
- @PreUpdate — Execute before an update event
In our example, we’ll add a changeNameToUpperCase() method to the Student entity class. The method changes the name field to uppercase. It is annotated by @PrePersist and @PreUpdate so that JPA will invoke this method before persisting and before updating:
@Entity
@Table(name = "student")
public class Student {
@PrePersist
@PreUpdate
private void changeNameToUpperCase() {
name = StringUtils.upperCase(name);
}
// The same definitions in our base class
}
Now, let us run the following code to persist a new Student entity and see how it works:
Student student = new Student();
student.setName("David Morgan");
entityManager.persist(student);
As we can see in the console log, the name parameter has been converted to uppercase before being included in the SQL query:
[main] DEBUG org.hibernate.SQL - insert into student (name,id) values (?,default)
Hibernate: insert into student (name,id) values (?,default)
[main] TRACE org.hibernate.orm.jdbc.bind - binding parameter (1:VARCHAR) <- [DAVID MORGAN]
3. JPA Entity Listeners
We defined the callback methods inside the entity class to handle JPA lifecycle events. This tends to be repetitive if we have more than one entity class that should implement the same logic. For example, we need to implement audit and logging features that are common for all entity classes, but it’s considered code duplication to define the same callback methods inside every entity class.
JPA provides an option to define an entity listener with those callback methods. An event listener decouples the JPA lifecycle callback methods from the entity class to alleviate code duplication.
Now let’s take a look at the same uppercase conversion scenario and apply the logic across different entity classes, but we will implement it this time using an event listener.
Let’s start by defining an interface Person as an extension to our solution for applying the same logic on multiple classes of entities:
public interface Person {
String getName();
void setName(String name);
}
This interface allows the implementation of a generic entity listener class that would apply to every Person implementation. Within the event listener, the method changeNameToUpperCase() has the @PrePersist and @PreUpdate annotations that convert the person’s name to uppercase before the persistence of the entity:
public class PersonEventListener<T extends Person> {
@PrePersist
@PreUpdate
private void changeNameToUpperCase(T person) {
person.setName(StringUtils.upperCase(person.getName()));
}
}
Now, to complete our configuration, we need to configure Hibernate to register our provider in the application. We are using Spring Boot in our example. Let’s add the integrator_provider property to the application.yaml:
@Entity
@Table(name = "student")
@EntityListeners(PersonEventListener.class)
public class Student implements Person {
// The same definitions in our base class
}
It does the exact same thing as the above example, but in a more reusable way: It moves the logic of converting uppercase out of the entity class itself and puts it into its entity listener class. Thus, we can apply this logic to any entity class implementing Person without any boilerplate code.
4. Hibernate Entity Listeners
Hibernate provides another mechanism for handling the entity lifecycle events via its dedicated event system. It allows us to define our event listeners and integrate them with Hibernate.
Our next example demonstrates a custom Hibernate event listener that listens for pre-insert and pre-update events by implementing the PreInsertEventListener and PreUpdateEventListener interfaces:
public class HibernateEventListener implements PreInsertEventListener, PreUpdateEventListener {
@Override
public boolean onPreInsert(PreInsertEvent event) {
upperCaseStudentName(event.getEntity());
return false;
}
@Override
public boolean onPreUpdate(PreUpdateEvent event) {
upperCaseStudentName(event.getEntity());
return false;
}
private void upperCaseStudentName(Object entity) {
if (entity instanceof Student) {
Student student = (Student) entity;
student.setName(StringUtils.upperCase(student.getName()));
}
}
}
Each one of these interfaces requires us to implement one event-handling method. In both methods, we’ll invoke the upperCaseStudentName() method. This custom event listener will attempt to intercept the name field and make it uppercase just before Hibernate inserts or updates.
After the definition of our event listener class, let’s define an Integrator class to register our custom event listener via Hibernate’s EventListenerRegistry:
public class HibernateEventListenerIntegrator implements Integrator {
@Override
public void integrate(Metadata metadata, BootstrapContext bootstrapContext,
SessionFactoryImplementor sessionFactoryImplementor) {
ServiceRegistryImplementor serviceRegistry = sessionFactoryImplementor.getServiceRegistry();
EventListenerRegistry eventListenerRegistry = serviceRegistry.getService(EventListenerRegistry.class);
HibernateEventListener listener = new HibernateEventListener();
eventListenerRegistry.appendListeners(EventType.PRE_INSERT, listener);
eventListenerRegistry.appendListeners(EventType.PRE_UPDATE, listener);
}
@Override
public void disintegrate(SessionFactoryImplementor sessionFactory,
SessionFactoryServiceRegistry serviceRegistry) {
}
}
In addition, we create a custom IntegratorProvider class that contains our integrator. This provider will be referenced in our Hibernate configuration to ensure our custom integrator is registered during the application startup:
public class HibernateEventListenerIntegratorProvider implements IntegratorProvider {
@Override
public List<Integrator> getIntegrators() {
return Collections.singletonList(new HibernateEventListenerIntegrator());
}
}
To complete our setup, we must configure Hibernate to register our provider in the application. We’re adopting Spring Boot in our example. Let’s add the property integrator_provider to the application.yaml:
spring:
jpa:
properties:
hibernate:
integrator_provider: com.baeldung.changevalue.entity.event.StudentIntegratorProvider
5. Hibernate Column Transformers
The last approach we’ll examine is the Hibernate @ColumnTransformer annotation. This annotation allows us to define an SQL expression that will apply to the target column.
In the code below, we annotate the name field by the @ColumnTransform applying the UPPER SQL function when Hibernate generates the SQL query that writes to the column:
@Entity
@Table(name = "student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column
@ColumnTransformer(write = "UPPER(?)")
private String name;
// getters and setters
}
This approach looks straightforward but has a major flaw. The transformation takes place at the database level only. If we insert a row into the table, we’ll see the following SQL with the UPPER function included in the console log:
[main] DEBUG org.hibernate.SQL - insert into student (name,id) values (UPPER(?),default)
Hibernate: insert into student (name,id) values (UPPER(?),default)
[main] TRACE org.hibernate.orm.jdbc.bind - binding parameter (1:VARCHAR) <- [David Morgan]
However, if we assert the name cases from the persisted entity, we can see the name in the entity is not in uppercase:
@Test
void whenPersistStudentWithColumnTranformer_thenNameIsNotInUpperCase() {
Student student = new Student();
student.setName("David Morgan");
entityManager.persist(student);
assertThat(student.getName()).isNotEqualTo("DAVID MORGAN");
}
It’s because the entity has already been cached in the EntityManager. Thus, it will return the same entity to us even if we retrieve it again. To get the updated entity with the transformation result, we need to first clear the cached entities by calling the clear() method on our EntityManager:
entityManager.clear();
Nonetheless, this will lead to an undesirable outcome because we are clearing all other stored cached entities.
6. Conclusion
In this article, we’ve explored various approaches to change a field value before persisting it in the database in Hibernate. These approaches include JPA lifecycle callbacks, JPA entity listeners, Hibernate event listeners, and Hibernate column transformers.
As usual, the complete source code is available over on GitHub.