Spring JDBC BeforeSaveCallback Example

When using Spring JDBC, there are a number of callbacks available to the developer that can be used to intercept various lifecycle stages of entities within the scope of JDBC. BeforeSaveCallback is used in Spring after an insert or update operation but before the actual database operation has occurred. This gives you the opportunity check various values contained within the entity object and perform modifications if desired.

In a typical Spring Boot application, you can simply define your BeforeSaveCallback bean in any of the classes located on the package scan path. From here, you can define any functionality you want to happen just before inserting or updating a record in the database. In the following example, we are automatically updating the id with a new UUID if the value has not yet been set.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
import org.springframework.data.relational.core.mapping.event.BeforeSaveCallback;

import java.util.UUID;

@SpringBootApplication
public class MySpringBootApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(MySpringBootApplication.class, args);
    }
    
    @Bean
    BeforeSaveCallback<Widget> beforeSaveCallback() {
        return (widget, mutableAggregateChange) -> {
            if (widget.getId() == null) {
                widget.setId(UUID.randomUUID().toString());
            }
            return widget;
        };
    }
     
}

As seen above, another useful example of Spring’s BeforeSaveCallback functional interface is generating String based IDs, an operation that isn’t supported by most databases. This allows you to interact with the database layer with the most minimal mount of intervention and assume the identifier will get generated automatically, eliminating the code smell of having this code in your business logic layer.

You can also define the functional callback without using a closure like so:

@Bean
BeforeSaveCallback<Widget> beforeSaveCallback() {
    return new BeforeSaveCallback<Widget>() {
        @Override
        public Widget onBeforeSave(Widget widget, MutableAggregateChange<Widget> mutableAggregateChange) {
            if (widget.getId() == null) {
                widget.setId(UUID.randomUUID().toString());
            }
            return widget;
        }
    };
}

Note that you can add multiple entity callbacks, effectively calling them sequentially. You can go one step further and define the order in which these callbacks happen by adding an @Order annotation to each bean like so:

import org.springframework.core.annotation.Order; 

...

@Bean
@Order(1)
BeforeSaveCallback<Widget> callbackA() { ... };

@Bean
@Order(2)
BeforeSaveCallback<Widget> callbackB() { ... };