Как Spring Data Jdbc определяет, что объект новый

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

В этом посте мы рассмотрим, как Spring Data Jdbc при сохранении объекта понимает: новая сущность и надо выполнить insert или такая сущность в базе данных уже есть и надо выполнить update.

Пост рассчитан на начинающих программистов и не содержит каких-то супер хитрых вещей.

Уже 13 ноября в OTUS пройдет demo-урок курса «Разработчик на Spring Framework» по теме: «Метрики и актуатор». По ссылке вы сможете бесплатно зарегистрироваться на урок. А прямо сейчас хочу поделиться с вами своей авторской статьей.




Прежде всего, определимся, почему задача определения «isNew» существует, откуда корни растут.

Допустим, у нас есть какой-то класс SomeObject. Объекты этого класса мы ходим сохранять в реляционной базе данных. Для этого мы создаем свой интерфейс:

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


Теперь для сохранения объекта мы можем воспользоваться методом save интерфейса RepositorySomeObject.

Разумеется, вновь возданный объект в базу данных попадет после выполнение Insert. Если же объект не новый, то надо выполнить update.
Spring Data Jdbc должен как-то решить, когда выполнить insert, а когда update.
Изучением того, как это делается мы и займемся.

В официальной документации (раздел 9.6.8. Entity State Detection Strategies) сказано, что есть три стратегии определения новизны объекта:

  • На основе поля Id
  • На основе имплементации интерфейса Persistable
  • На основе имплементации интерфейса EntityInformation


Первые два используются сплошь и рядом, поэтому именно их мы и рассмотрим.

Начнем с подготовки объекта.


Создадим класс class SomeObject, добавим в него несколько полей.

Одно из которых:

@Id
private final Long id;


Обратите внимание на аннотацию Id, это важно.

Класс есть, интерфейс RepositorySomeObject мы тоже уже создали, можно сохранять в базу данных.
Для краткости, тут я не буду расписывать, как создается таблица, и в целом пропущу устройство проекта. По ссылке в конце статьи можно будет посмотреть все целиком.

Для изучения вопроса создадим пару тестов, и в них будем сохранять объект.
Для теста мы с помощью TestContainers поднимем Postgresql. На этом аспекте я тоже не буду останавливаться, можно будет все посмотреть в коде.

Тест у нас такой:

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

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


Новый объект сохраняем и проверяем, что Id заполнился.
Запускаем тест и смотрим, как Spring Data Jdbc понимает, что в данном случае нам надо выполнить именно insert, а не update.

Точка принятия решения находится в классе

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


вот фрагмент этого метода:

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

return store(instance, changeCreator, persistentEntity);


Вся суть находится в persistentEntity.

Заглянем в persistentEntity.isNew, там увидим:
public boolean isNew(Object bean) {
        this.verifyBeanType(bean);
        return ((IsNewStrategy)this.isNewStrategy.get()).isNew(bean);
}


Получается, что есть некая «стратегия», которая и определяет, новый объект или нет.
Вопрос сводится к изучению, что это такое.

Обратимся к определению стратегии.


Это конструктор класса: org.springframework.data.mapping.model.

Вот фрагмент:

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


Что тут происходит?


Если сохраняемый объект имплементирует интерфейс PersistableIsNewStrategy, то используется соответствующая стратегия (об этом мы еще поговорим), если нет, то работает логика, представленная в методе getFallbackIsNewStrategy. Давайте на этом моменте остановимся подробнее.

Немного пройдем по цепочке вызовов getFallbackIsNewStrategy и окажемся в методе
public boolean isNew(Object entity) класса PersistentEntityIsNewStrategy.

В этом методе и определяется – новый объект или нет.
Для этого берется значение поля, отмеченного аннотацией Id.
Если значение null – значит объект новый.
Если не null, то возможно варианты.
Если это не примитивный тип данных, значит все понятно – это объект не новый.
Если тип данных примитивный, то он по определению не может быть null, и выполняется проверка на 0.

Еще раз сформулируем работу этой стратегии.

  1. Берем значение поля Id
  2. Если null – объект новый
  3. Иначе, если не примитивный тип, значит – объект не новый.
  4. Если примитивный тип и значение 0, то новый, иначе не новый.


Получается, все довольно просто и логично. У нового объекта идентификатора нет, поэтому он и новый. Значение ключевого поля формируется на стороне базы данных и возвращается вместе с сохраненным объектом.

А что делать, если по каким-то причинам Id-шник надо сформировать в java-коде и передать в базу данных. В этом случае даже в новом объекте поле идентификатора будет заполнено и описанная выше стратегия уже не сработает.

Что делать в этой ситуации?


Использовать вторую стратегию, основанную на интерфейсе Persistable.
У этого интерфейса есть два метода getId и isNew.
Чтобы воспользоваться этим механизмом надо у объекта, который мы хотим сохранить, имплементировать этот интерфейс и самостоятельно определить, когда объект новый, а когда нет.

При выполнении кода:

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


Spring определит, что сохраняемый объект имплементирует интерфейс Persistable и вызовет метод isNew.

Подведем итоги.


Если идентификатор объекта формируется на стороне базы данных (наверное, самый частый случай), то достаточно на поле с идентификатором поставить аннотацию Id.
Если идентификатор создается на стороне приложения, то такой объект должен имплементировать интерфейс Persistable и реализовать метод isNew.

  • Полный пример находится по этой ссылке.
  • Видео-разбор можно посмотреть тут.


Источник: https://habr.com/ru/company/otus/blog/526030/


Интересные статьи

Интересные статьи

«УРАЛХИМ» делает удобрения. № 1 в России — по производству аммиачной селитры, например, входит в топ-3 отечественных производителей аммиака, карбамида, азотных удобрений. Выпускаются ...
Хэллоуин — время призраков, гоблинов, упырей и другой жуткой нечисти. Но во Вселенной нет ничего более пугающего, чем черные дыры. Они огромны, неизведанны и практически ...
Итоги прошедшей недели на Хабре. В этом дайджесте — самые важные, интересные и громкие события, о которых мы говорили в последние семь дней. Рамблер вспомнил об Nginx спустя 15 лет; Intel объ...
Продолжение статьи Как выглядит zip-архив и что мы с этим можем сделать. Предисловие Доброго времени суток. И снова в эфире у нас нетрадиционное программирование на PHP. В прошлой статье уваж...
В среде SRE-/DevOps-инженеров никого не удивишь, что однажды появляется клиент (или система мониторинга) и сообщает, что «всё пропало»: сайт не работает, оплаты не проходят, жизнь — тлен… Как...