Spring Data JPA’s repositories provide many methods that implement the standard operations you need to create a basic persistence layer. One of them is the save(S entity) method, which the CrudRepository defines. When you call this method, it depends on the entity object’s state which database operation Spring Data JPA performs. Spring Data JPA stores a new record in the database if you call the method with a new entity object. And if you provide an object that has already been persisted in the database, Spring Data JPA executes an SQL UPDATE statement instead.
To decide which of these 2 operations it has to perform, Spring Data JPA needs to find out if the entity object represents an existing database record or not. This is called state detection and gets triggered by the save(S entity) implementation in Spring Data JPA’s SimpleJpaRepository class.
/* * (non-Javadoc) * @see org.springframework.data.repository.CrudRepository#save(java.lang.Object) */ @Transactional @Override public <S extends T> S save(S entity) { Assert.notNull(entity, "Entity must not be null."); if (entityInformation.isNew(entity)) { em.persist(entity); return entity; } else { return em.merge(entity); } }
The call of the isNew(S entity) method returns the detected state. Spring Data JPA supports 3 different strategies to detect the state of an entity, which I will show you in the following sections.
The default state detection strategy relies on your entity’s properties. If it includes a version property, Spring Data JPA uses it to detect the state. Otherwise, it uses the primary key attribute.
If you’re using optimistic locking to prevent concurrent modifications, you annotate one of your entity’s properties with @Version. Your persistence provider, which in most cases is Hibernate, then uses that property to track the version of that object and its mapped database record. The JPA specification defines different ways and data types to track the version of an entity. The easiest and most efficient one is a simple counter that your persistence provider increments during each write operation.
Spring Data JPA also uses the version property to detect new entity objects it needs to persist. The persistence provider manages the property’s value and sets it for the first time when persisting the entity. Due to that, the version property of a new entity object is null. And if it contains any value, the entity object maps an existing database record, which Spring needs to update.
The primary key-based state detection is very similar to the version-based approach. The only difference is that Spring Data JPA checks if the primary key attribute is null instead of checking the version attribute. If it’s null, Spring treats the entity as a new object and persists it. Otherwise, it expects the entity to be an existing one and updates the corresponding database record.
If you ever used an entity class with a programmatically assigned primary key, you might have already recognized a downside of this approach: It only works for automatically assigned primary keys, e.g., when using a database sequence or autoincrement column.
The reason for that is that if you assign the primary key value of a new entity object programmatically, you need to do that before calling the save method on your repository. Otherwise, the primary key value will not be set when your persistence provider persists entity object. But if you set it before calling the save method, Spring Data JPA can’t detect that you want to persist a new entity object.
The Persistable interface provides a simple option to customize the state detection algorithm used for a specific entity class. It defines the isNew() method, which Spring Data JPA calls to determine the state of an entity object. By implementing that method, you can adjust the detection algorithm to the specific needs of your domain model.
The following code snippet shows an implementation commonly used with entity classes that use a programmatically assigned primary key and no version attribute. As explained in the previous section, Spring Data JPA’s default detection algorithm can’t handle these entities.
In that case, you can use a transient boolean attribute to track the entity’s state and return it in the isNew() method. When you create a new object, the attribute gets initialized with true. The @PostLoad and @PrePersist annotations on the trackNotNew() method ensure that your persistence provider calls this method after it fetched an entity object from the database or before it persists it. The method then changes the isNew flag to false.
@Entity public class ChessGame implements Persistable<Long> { @Id private Long id; @Transient private boolean isNew = true; @Override public boolean isNew() { return isNew; } @PostLoad @PrePersist void trackNotNew() { this.isNew = false; } ... }
If you need this type of check for multiple entity classes, I recommend modeling and extending a @MappedSuperclass that provides the isNew attribute and both methods.
Implementing the EntityInformation interface is not a commonly used or recommended approach to customize the state detection algorithm. Most teams either rely on the default algorithm or let the entity classes implement the Persistable interface. Because of that, I’m only explaining this approach on a theoretical level and recommend using a @MappedSuperclass that implements the Persistable interface.
To use this approach, you need to implement the EntityInformation interface. You also need to extend the JpaRepositoryFactory class and override the getEntityInformation method to return your EntityInformation implementation.
Spring’s SimpleJpaRepository class will then call your implementation of the EntityInformation interface to detect the state of every entity object. So, better make sure to use a fast and generic algorithm that works for all your entity classes.
When you call Spring Data JPA’s save(S entity) method, it has to decide if you provided a new entity object it needs to persist or if it has to update an existing database record. This process is called state detection.
By default, Spring Data JPA checks if the entity object has a version attribute. If that’s the case and the attribute’s value is null, it considers the entity a new object. In case your entity doesn’t have a version attribute, Spring Data checks the primary key attribute. If the primary key is null, Spring Data JPA persists the object as a new entity. Otherwise, it tries updating an existing record.
If you want to customize the state detection algorithm, your entity class must implement the Persistable interface with its isNew() method. Within that method, you can access all attributes of your entity and implement your own check. Developers often do this for entity classes that use a programmatically assigned primary key and don’t have a version attribute.