|

How to change an attribute before INSERT and UPDATE


Take your skills to the next level!

The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, monthly Java Persistence News, monthly coding problems, and regular expert sessions.


In a comment here on the blog, Mehmet asked how he could call a function before inserting or updating an entity in the database.

There are several ways you can call a function as part of an SQL INSERT or UPDATE statement. But these are not your only options. To change an attribute’s value before the statement gets executed, you can 

  1. add code to the setter method,
  2. implement a lifecycle callback,
  3. define a Hibernate-specific @ColumnTransformer,
  4. provide custom SQL INSERT and UPDATE statements.

Let’s take a look at all 4 options.

Option 1: Customize your Setter Method

By far, the easiest approach to adapt the value of an attribute before it gets persisted is to add the required code to the setter method. I use this in the following code snippet to trim leading and trailing whitespaces when setting the description attribute.

@Entity
public class Book {

    ...

    private String description;

    public void setDescription(String description) {
        this.description = description.trim();
    }
	
    ...
}

This approach’s advantage is that it doesn’t require any external trigger to trim the description String. But it also has the disadvantage that you execute this operation every time the setter method gets called.

To prevent Hibernate from triggering this operation every time it gets an entity from the database, you should annotate your primary key attribute with the @Id annotation. This tells Hibernate to use field-based access for this entity. It will then use reflection to set the entity attributes instead of the getter methods.

Option 2: Lifecycle Callback

As I explained in a previous article, each entity object has a lifecycle. Depending on the operation performed, the object changes its lifecycle state. You can implement lifecycle callbacks that Hibernate triggers when an entity changes its lifecycle state.

Suppose you want to trigger an operation before Hibernate executes an SQL INSERT or UPDATE statement for a specific entity class. In that case, you can add a custom method to that class and annotate it with @PreInsert and @PreUpdate. This tells Hibernate to call that method after you called the EntityManager.persist method, but before executing the SQL UPDATE statement.

@Entity
public class Book {

    ...

    private String description;

    @PrePersist
    @PreUpdate
    void removeWhitespace() {
        this.description = this.description.trim();
    }
	
    ...
}

Within this method, you can only perform operations on that entity object. This might be a limitation in some situations. But it’s more than good enough to implement a validation or change a value before it gets persisted.

As you can see, I annotated the removeWhitespace method with @PrePersist and @PreUpdate. It contains the same code as I added to the setter method in the previous section. Please keep in mind that the code in your lifecycle callback doesn’t get called until you persist a new entity, or Hibernate executes the SQL UPDATE statement. In the example, that means my business code can’t expect that the String in the description attribute has been trimmed. It needs to be able to handle trimmed and untrimmed descriptions.

Option 3: @ColumnTransformer

To trigger a database function instead of implementing it in your Java code, you can use a @ColumnTransformer. It enables you to define a native SQL snippet used for all write operations. You can also provide one for each read operation.

In this article’s example, you could call the database function trim to remove leading and trailing whitespace from the description while writing it to the database. And because there is no need for any adjustments during read operations, you don’t need to specify the read attribute of the @ColumnTransformer annotation.

@Entity
public class Book {

    ...

    @ColumnTransformer(write = "trim(?)")
    private String description;
    
    ...
}

This is very similar to the @Formula annotation, which Mehmet mentioned in his question. This approach’s main advantage is that you provide a native SQL snippet that Hibernate embeds in all write operations. That enables you to use all features supported by your database and doesn’t limit you to Hibernate’s feature set. But if you deploy your application using multiple DBMS, you also need to make sure that all of them support the database function.

Option 4: Custom SQL INSERT and UPDATE statements

And the 4th option is to define custom SQL INSERT and UPDATE statements for the entity class. This approach is similar to the one I described in my article about implementing a soft-delete with Hibernate.

Hibernate’s @SQLInsert and @SQLUpdate statements enable you to define the native SQL statement used to persist and update an entity. You can use them to call a database function that changes the value of an entity attribute while persisting it in the database.

@Entity
@SQLInsert(sql = "INSERT INTO Book (description, title, version, id) VALUES (trim(?), ?, ?, ?)")
@SQLUpdate(sql = "UPDATE Book SET description = ?, title = ?, version = ? WHERE id = ? AND version = ?")
public class Book { ... }

This approach is similar to the previously described @ColumnTransformer. In both cases, you are adjusting the SQL statement that inserts or updates the database records. But providing your own INSERT and UPDATE statements for an entity class is more complex than only providing the SQL snippet used for a specific attribute. Because of that, I prefer a @ColumnTransformer over a customized INSERT and UPDATE statement.

Conclusion

You have multiple options to adapt the value of an entity attribute before persisting it in the database.

The easiest one is to add the required code to the setter method of your entity class. If you use field-based access and make sure that you don’t call this method multiple times within the same use case, this approach doesn’t have any side-effects. Because of that, this is my preferred approach to adapt the value of an entity attribute.

A lifecycle callback is your 2nd best option to change an entity attribute before persisting it. You can tell Hibernate to trigger it when you persist a new entity object and before it executes the SQL UPDATE statement. But please keep in mind that Hibernate tries to delay an UPDATE statement’s execution as long as possible. Because of that, your business logic needs to be able to handle values that haven’t been adjusted by the lifecycle callback.

If you can’t easily implement the required operations in your Java code, you can use a database function instead. The best way to do that is to use a @ColumnTransformer. It defines a native SQL snippet that Hibernate embeds in each SQL INSERT and UPDATE statement.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.