How Spring Data Jdbc determines when an object is new

In this post, we will look at how Spring Data Jdbc, when saving an object, understands: a new entity and you need to insert, or such an entity already exists in the database and you need to perform an update.

The post is designed for novice programmers and does not contain any super tricky things.

Already on November 13, OTUS will host demo-lesson of the course “Developer on Spring Framework” on this topic: “Metrics and actuator”Link you can register for the lesson for free. And right now I want to share my author’s article with you.


First of all, let’s define why the task of determining “isNew” exists, where the roots grow from.

Let’s say we have some kind of SomeObject class. We want to store objects of this class in a relational database. To do this, we create our own interface:

@Repository
public interface RepositorySomeObject extends CrudRepository<SomeObject, Long> {
}

Now we can use the save method of the RepositorySomeObject interface to save the object.

Of course, the newly returned object will get into the database after the execution of Insert. If the object is not new, then update must be performed.
Spring Data Jdbc has to decide somehow when to execute insert and when to update.
We will study how this is done.

In the official documentation (section 9.6.8. Entity State Detection Strategies) it is said that there are three strategies for determining the novelty of an object:

  • Based on the Id field
  • Based on the implementation of the Persistable interface
  • Based on the implementation of the EntityInformation interface

The first two are used all the time, which is why we will consider them.

Let’s start by preparing the object.

Let’s create a class class SomeObject, add several fields to it.

One of which:

@Id
private final Long id;

Pay attention to the annotation Id, it is important.

There is a class, we have already created the RepositorySomeObject interface, you can save it to the database.
For the sake of brevity, here I will not describe how the table is created, and in general I will skip the project structure. The link at the end of the article will show you the whole story.

To study the issue, we will create a couple of tests, and we will save the object in them.
For the test, we will raise Postgresql using TestContainers. I will not dwell on this aspect either, it will be possible to look at everything in the code.

Our test is like this:

    @Test
    void saveTestWithNullOrZeroId() {
        var object = new SomeObject(null, "name", "value");

        var savedObject = repository.save(object);
        assertThat(savedObject).isNotNull();
        assertThat(savedObject.getId()).isNotNull();
    }

We save the new object and check that the Id is filled.
Let’s run the test and see how Spring Data Jdbc understands that in this case we need to execute insert, not update.

The decision point is in the classroom

org.springframework.data.jdbc.core.JdbcAggregateTemplate
метод: public <T> T save(T instance)

here is a snippet of that method:

Function<T, MutableAggregateChange<T>> changeCreator = 
persistentEntity.isNew(instance) ? this::createInsertChange : this::createUpdateChange;

return store(instance, changeCreator, persistentEntity);

The whole point is in persistentEntity.

Let’s take a look at persistentEntity.isNew, we will see there:

public boolean isNew(Object bean) {
        this.verifyBeanType(bean);
        return ((IsNewStrategy)this.isNewStrategy.get()).isNew(bean);
}

It turns out that there is a certain “strategy” that determines whether a new object or not.
The question boils down to learning what it is.

Let’s turn to the definition of a strategy.

This is the constructor of the class: org.springframework.data.mapping.model

Here’s a snippet:

this.isNewStrategy = Lazy.of(() -> 
Persistable.class.isAssignableFrom(information.getType()) 
		? PersistableIsNewStrategy.INSTANCE
		: getFallbackIsNewStrategy());

What’s going on here?

If the persistable object implements the PersistableIsNewStrategy interface, then the appropriate strategy is used (we’ll talk about this later), if not, then the logic presented in the getFallbackIsNewStrategy method works. Let’s dwell on this point in more detail.

Let’s walk a little through the chain of calls to getFallbackIsNewStrategy and find ourselves in the method
public boolean isNew (Object entity) of the PersistentEntityIsNewStrategy class.

This method determines whether a new object is or not.
For this, the value of the field marked with the Id annotation is taken.
If the value is null, then the object is new.
If not null, then there are options.
If this is not a primitive data type, then everything is clear – this object is not new.
If the data type is primitive, then it cannot be null by definition, and a check for 0 is performed.

Let us once again formulate the work of this strategy.

  1. We take the value of the Id field
  2. If null – the object is new
  3. Otherwise, if it is not a primitive type, then the object is not new.
  4. If primitive type and value 0, then new, otherwise not new.

It turns out that everything is quite simple and logical. The new object does not have an identifier, so it is new. The key field value is generated on the database side and returned along with the stored object.

And what if, for some reason, the Id-shnik needs to be generated in java code and transferred to the database. In this case, even in a new object, the identifier field will be filled in and the strategy described above will no longer work.

What to do in this situation?

Use the second strategy based on the Persistable interface.
This interface has two methods getId and isNew.
To use this mechanism, it is necessary for the object that we want to save, implement this interface and independently determine when the object is new and when not.

When executing the code:

this.isNewStrategy = Lazy.of(() -> 
Persistable.class.isAssignableFrom(information.getType()) 
		? PersistableIsNewStrategy.INSTANCE
		: getFallbackIsNewStrategy());

Spring will determine that the persistent object implements the Persistable interface and will call the isNew method.

Let’s summarize.

If the object identifier is generated on the database side (probably the most common case), then it is enough to put the Id annotation on the field with the identifier.
If the identifier is created on the application side, then such an object must implement the Persistable interface and implement the isNew method.

  • A complete example can be found at this link
  • Video analysis can be viewed here

Similar Posts

Leave a Reply

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