Dynamic Inserts and Updates with Spring Data JPA
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.
When you persist a new entity or update an existing one with Spring Data JPA, you might have recognized that you’re always executing the same SQL statement that sets all columns mapped by the entity. That’s even the case if you only update one of its attributes.
That is a performance optimization provided by Hibernate, the JPA implementation that Spring Data JPA uses by default. Hibernate tries to avoid checking which attributes of an entity have changed and generating a specific SQL statement for them. It instead generates 1 SQL UPDATE and 1 SQL INSERT statement for each entity class at startup and reuses it for every insert or update operation.
Reusing the same statement over and over again improves Hibernate’s work. But it also creates some side effects. These statements create an overhead if you only change 1 attribute of a huge entity class. They also cause problems if you need to audit all changes performed on a database table. In these cases, it might be better to let Hibernate generate a new SQL INSERT or UPDATE statement for each operation.
Standard behavior
But before I show you how to do that, let’s take a quick look at the default behavior. Here, you can see a simple ChessPlayer entity that stores each player’s firstName, lastName, and birthDate. The id attribute maps the primary key, and its values get generated by a database sequence.
@Entity
public class ChessPlayer {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "player_seq")
@SequenceGenerator(name = "player_seq", sequenceName = "player_sequence")
private Long id;
private String firstName;
private String lastName;
private LocalDate birthDate;
...
}
I prepared a standard repository that only extends Spring Data JPA’s JpaRepository and doesn’t add any custom queries or other functionality.
public interface ChessPlayerRepository extends JpaRepository<ChessPlayer, Long> { }
And I prepared a test case that persists a new ChessPlayer without setting his birthDate attribute. In the next step, I fix a typo in the firstName. This will trigger an additional SQL UPDATE statement.
ChessPlayer p = new ChessPlayer();
p.setFirstName("Torben");
p.setLastName("Janssen");
chessPlayerRepository.save(p);
p.setFirstName("Thorben");
As you can see in the log output, Hibernate executed an SQL INSERT and an UPDATE statement that set all columns of the Chess_Player table. This includes the birth_date column, which gets set to null.
11:33:15.505 DEBUG 19836 --- [ main] org.hibernate.SQL : select nextval ('player_sequence')
11:33:15.514 DEBUG 19836 --- [ main] org.hibernate.SQL : select nextval ('player_sequence')
11:33:15.547 DEBUG 19836 --- [ main] org.hibernate.SQL : insert into chess_player (birth_date, first_name, last_name, id) values (?, ?, ?, ?)
11:33:15.557 DEBUG 19836 --- [ main] org.hibernate.SQL : update chess_player set birth_date=?, first_name=?, last_name=? where id=?
Hibernate’s @DynamicInsert
Spring Data JPA acts as a layer on top of Hibernate. Due to that, you can use all of Hibernate’s proprietary mapping annotations on your entity classes.
If you want to dynamically generate the SQL INSERT statement when persisting a new entity object, you need to annotate the entity class with Hibernate’s proprietary @DynamicInsert annotation.
@Entity
@DynamicInsert
public class ChessPlayer {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "player_seq")
@SequenceGenerator(name = "player_seq", sequenceName = "player_sequence")
private Long id;
private String firstName;
private String lastName;
private LocalDate birthDate;
...
}
When you then execute the same test case as before, you will see in the log output that Hibernate generated the SQL INSERT statement dynamically using only the attributes set on the new entity object.
ChessPlayer p = new ChessPlayer();
p.setFirstName("Torben");
p.setLastName("Janssen");
chessPlayerRepository.save(p);
p.setFirstName("Thorben");
Hibernate only sets the id, first_name, and last_name columns in the SQL INSERT statement, but not the birth_date column. Hibernate excluded that column because I didn’t set it in the test case before calling the save method on Spring Data JPA’s repository.
11:37:20.374 DEBUG 7448 --- [ main] org.hibernate.SQL : select nextval ('player_sequence')
11:37:20.386 DEBUG 7448 --- [ main] org.hibernate.SQL : select nextval ('player_sequence')
11:37:20.427 DEBUG 7448 --- [ main] org.hibernate.SQL : insert into chess_player (first_name, last_name, id) values (?, ?, ?)
11:37:20.435 DEBUG 7448 --- [ main] org.hibernate.SQL : update chess_player set birth_date=?, first_name=?, last_name=? where id=?
But the SQL UPDATE statement still updates all columns mapped by the ChessPlayer entity class. If you want to change that, you also need to annotate the entity class with @DynamicUpdate.
Hibernate’s @DynamicUpdate
Like the @DynamicInsert annotation described in the previous section, the @DynamicUpdate annotation tells Hibernate to generate a specific SQL UPDATE statement for each update operation. When doing that, Hibernate detects which attributes have changed and only includes these in the SQL statements.
In the following example, I annotated the ChessPlayer entity with Hibernate’s @DynamicInsert and @DynamicUpdate annotation.
@Entity
@DynamicInsert
@DynamicUpdate
public class ChessPlayer {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "player_seq")
@SequenceGenerator(name = "player_seq", sequenceName = "player_sequence")
private Long id;
private String firstName;
private String lastName;
private LocalDate birthDate;
...
}
Let’s execute the same test case as in the previous examples.
ChessPlayer p = new ChessPlayer();
p.setFirstName("Torben");
p.setLastName("Janssen");
chessPlayerRepository.save(p);
p.setFirstName("Thorben");
As you can see in the log output, Hibernate now generated specific SQL INSERT and UPDATE statements.
11:39:45.177 DEBUG 13832 --- [ main] org.hibernate.SQL : select nextval ('player_sequence')
11:39:45.185 DEBUG 13832 --- [ main] org.hibernate.SQL : select nextval ('player_sequence')
11:39:45.214 DEBUG 13832 --- [ main] org.hibernate.SQL : insert into chess_player (first_name, last_name, id) values (?, ?, ?)
11:39:45.224 DEBUG 13832 --- [ main] org.hibernate.SQL : update chess_player set first_name=? where id=?
We already discussed the INSERT statement in the previous section, so let’s focus on the update operation.
In the test case, I only changed the value of the firstName attribute. Hibernate recognized that when it performed a dirty check on that entity object. Based on that, Hibernate then generated an SQL UPDATE statement that only changes the value in the first_name column.
Conclusion
Spring Data JPA acts as a layer on top of a JPA implementation. In most cases, that’s Hibernate. When you persist or update an entity object, Spring Data JPA delegates that operation to the JPA implementation. Due to that, the handling of all write operations and the generation of SQL statements depends on your JPA implementation and its capabilities.
By default, Hibernate doesn’t generate a specific SQL INSERT or UPDATE statement for each entity object. Instead, it generates 1 INSERT and 1 UPDATE statement for each entity class at application startup and reuses them for all insert or update operations. That reduces the overhead of these operations but also changes too many columns in the database.
If that’s a problem, you can annotate your entity class with @DynamicInsert and @DynamicUpdate. These proprietary annotations tell Hibernate to dynamically generate the SQL INSERT or UPDATE statement for each entity object. When doing that, please keep in mind that you don’t get this for free and can’t activate or deactivate it for specific use cases. To generate a specific UPDATE or INSERT statement, Hibernate needs to detect which attributes have changed and generate a new SQL statement based on this information. That slows down all insert or update operations for objects of this entity class.
nice post
I started reading your post from the beginning, and it was quite interesting to read. I appreciate you providing such a nice blog, and I hope you continue to update it on a regular basis.
Thanks, nice tips