CRUD.save () с отметкой времени из БД

У меня есть приложение Spring, поддерживаемое H2 и SqlServer. Я установил столбец сущности, например:

@Column(insertable = false, columnDefinition = "bigint default CURRENT_TIMESTAMP")

Теперь, когда я сохраняю новую сущность, временная метка устанавливается правильно. Однако, когда я изменяю объект, метка времени будет той, которую я установил для предварительного обновления объекта. Я знаю, что если я установил updatable = false, он снова обновится до current_timestamp, однако мне нужна гибкость, чтобы временная метка всегда устанавливалась только в случае значительного изменения, IE, если я изменяю поле комментария для объекта, я не хочу метка времени должна измениться, но если я изменю поле владельца, я хочу, чтобы метка времени была обновлена.

Я попытался установить значение null, но это ничего не дает. Я не уверен, как я могу использовать метод @PreUpdate, чтобы использовать CURRENT_TIMESTAMP из db, и я не знаю, как легко изменить crud.save (). Единственная альтернатива, о которой я могу думать, - это добавить customSave с помощью @Modifying с собственным запросом, но это будет последнее средство.

Примечание: я должен использовать временную метку БД, точность очень важна, строки должны сохраняться в порядке с увеличивающимися временными метками, а смещение времени сервера приводит к условиям гонки.

Если вы используете временную метку, чтобы повлиять на некоторый порядок, я бы подумал, что первичный ключ суррогатной идентичности / последовательности решает эту проблему довольно хорошо и автоматически индексируется в виде b-дерева, что позволяет при необходимости выполнять быструю сортировку. Как показывает мой ответ, это позволяет основывать поведение временной метки на бизнес-требованиях, а не пытаться улучшить, вероятно, 2 потенциально несвязанные группы требований в одном и том же поле.

Naros 30.05.2018 16:13
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
1
148
2

Ответы 2

Учитывая ваш список требований, у вас реально есть 3 варианта

  1. Используйте триггер базы данных в своей таблице для управления значениями метки времени обновления.
  2. Используйте обратный вызов @PreUpdate.
  3. Сделайте это как часть вашей службы, когда у вас есть доступ к состоянию обновления и существующему состоянию.

Вариант (1) довольно прост в том, что у вас есть как текущее состояние, так и состояние обновления в триггере вашей базы данных, и вы можете выполнять любые операции, необходимые для определения, следует ли вам устанавливать значение или использовать существующее значение во время обновления.

Единственным недостатком между (1) и (2) является то, что (2) нет будет основываться на времени базы данных, а скорее на времени приложения, потому что вы будете делать это на сервере приложений. Но (2) имеет то преимущество, что ваш код для этого будет независимым от базы данных, что позволит вам развернуть его где угодно на любой платформе базы данных, которую поддерживает Hibernate или имеет функционирующий диалект, без каких-либо проблем.

Мое личное мнение, выберите (2), потому что он также довольно сфокусирован на предметно-ориентированном дизайне.

Для реализации (2) это довольно просто.

@Entity
public class YourSuperDumperEntity {

  // Clear this value on load
  @PostLoad
  public void postLoadClearFlags() {
    fieldValuesChangedThatRequireTimestampUpdate = false;
  }

  // This handles the setting of the value at update-time
  @PreUpdate
  public void preUpdateCallbackAdjustTimestampOnChanges() {
    if ( fieldValuesChangedThatRequireTimestampUpdate ) {
      updateTimestamp = new Date();
    }
  }


  public void setSomeFieldThatTriggersUpdateTimestamp(String value) {
    // check if the old and new value differ enough to trigger update date
    // if so, set the boolean flag here as follows
    this.fieldValuesChangedThatRequireTimestampUpdate = true;

    // now set the value
    this.someFieldThatTriggersUpdateTimestamp = value;
  }
}

Как я указывал ранее, прелесть здесь заключается в том, что вся логика этого самодостаточна в одном классе, которому она принадлежит, потому что то, о чем вы говорите, - это состояние сущности.

Возможно, вам придется немного поиграть с обратными вызовами JPA, чтобы удовлетворить ваши потребности, но идея остается той же.

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

Спасибо выше за предложения, я боролся большую часть сегодняшнего дня с этим нелегким решением, Naros ваше предложение триггера ближе всего к работоспособному, однако оказывается, что независимо от источника метки времени, который я нашел, если я не включу чтение без фиксации, которое не вариант, существует проблема, что транзакции могут занимать до нескольких сотен миллисекунд, и поэтому существует слишком большое состояние гонки, чтобы доверять порядку меток времени, поскольку он назначается при сохранении, а не завершении транзакции. Я решил добавить новую таблицу, которая будет действовать как очередь и помещать туда идентификатор из основной таблицы каждый раз, когда обновляется временная метка, менее эффективна, но безопасна для транзакций без условий гонки.

Другие вопросы по теме