Java - JPA - аннотация @Version

118

Как работает @Versionаннотация в JPA?

Я нашел несколько ответов, выдержка из которых выглядит следующим образом:

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

Но я до сих пор не понимаю, как это работает.


Также как из следующих строк:

Вы должны считать поля версии неизменными. Изменение значения поля приводит к неопределенным результатам.

Означает ли это, что мы должны объявить поле версии как final?

Ятендра Гоэль
источник
1
Все это делает чек / обновление версии в каждом запросе обновления: UPDATE myentity SET mycolumn = 'new value', version = version + 1 WHERE version = [old.version]. Если кто-то обновил запись, old.versionона больше не будет соответствовать записи в БД, и предложение where предотвратит обновление. Будут обновлены строки 0, которые JPA может обнаружить, чтобы сделать вывод, что произошла одновременная модификация.
Stijn de Witt

Ответы:

189

Но все же я не уверен, как это работает?

Допустим, у объекта MyEntityесть аннотированное versionсвойство:

@Entity
public class MyEntity implements Serializable {    

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @Version
    private Long version;

    //...
}

При обновлении поле, помеченное значком, @Versionбудет увеличено и добавлено к WHEREпредложению, примерно так:

UPDATE MYENTITY SET ..., VERSION = VERSION + 1 WHERE ((ID = ?) AND (VERSION = ?))

Если WHEREпредложение не соответствует записи (поскольку та же сущность уже была обновлена ​​другим потоком), то поставщик сохраняемости выдаст файл OptimisticLockException.

Означает ли это, что мы должны объявить наше поле версии окончательным?

Нет, но вы можете подумать о том, чтобы сделать сеттер защищенным, поскольку вы не должны это называть.

Паскаль Тивент
источник
5
Я получил исключение нулевого указателя с этим фрагментом кода, потому что Long инициализируется как null, вероятно, его следует инициализировать явно с помощью 0L.
Маркус
2
Не полагайтесь на автоматическую распаковку longили просто на вызов longValue(). Вам нужны явные nullпроверки. Я бы не стал делать явную инициализацию его самостоятельно, так как это поле должно управляться поставщиком ORM. Я думаю, что он устанавливается 0Lна первую вставку в БД, поэтому, если он установлен, nullэто говорит вам, что эта запись еще не сохранена.
Stijn de Witt,
Помешает ли это создать объект, который (пытались) создать более одного раза? Я имею в виду, что сообщение темы JMS запускает создание объекта, и есть несколько экземпляров приложения, которое прослушивает сообщение. Я просто хочу избежать уникальной ошибки нарушения ограничений ..
Ману
Извините, я понимаю, что это старый поток, но учитывает ли @Version также грязный кеш в кластерных средах?
Шон Эйон Смит
3
При использовании Spring Data JPA может быть лучше использовать оболочку, Longа не примитив longдля @Versionполя, поскольку поле SimpleJpaRepository.save(entity)с нулевой версией, скорее всего, будет рассматриваться как индикатор того, что сущность является новой. Если объект новый (на что указывает значение null), Spring вызовет em.persist(entity). Но если у версии есть значение, она вызовет em.merge(entity). По этой же причине поле версии, объявленное как тип-оболочка, следует оставить неинициализированным.
rdguam
33

Хотя ответ @Pascal совершенно верен, по моему опыту я считаю, что приведенный ниже код полезен для выполнения оптимистической блокировки:

@Entity
public class MyEntity implements Serializable {    
    // ...

    @Version
    @Column(name = "optlock", columnDefinition = "integer DEFAULT 0", nullable = false)
    private long version = 0L;

    // ...
}

Зачем? Так как:

  1. Оптимистическая блокировка не будет работать, если для поля, помеченного значком, @Versionслучайно установлено значение null.
  2. Поскольку это специальное поле не обязательно бизнес - версия объекта, чтобы избежать вводящих в заблуждение, я предпочитаю называть такое поле , что - то вроде , optlockа не version.

Первая точка не имеет значения , если пользы приложения только JPA для вставки данных в базу данных, так как JPA поставщик будет обеспечивать 0для @versionполя во время создания. Но почти всегда используются простые операторы SQL (по крайней мере, во время модульного и интеграционного тестирования).

Г. Демеки
источник
Я называю это u_lmod(последний измененный пользователем), где пользователь может быть человеком или определенным (автоматизированным) процессом / объектом / приложением (так что дополнительное хранение также u_lmod_idможет иметь смысл). (На мой взгляд, это очень простой бизнес-мета-атрибут). Обычно он сохраняет свое значение как ДАТА (ВРЕМЯ) (что означает информацию о году ... миллисекундах) в формате UTC (если «местоположение» часового пояса редактора важно сохранить, то с часовым поясом). Кроме того, очень полезно для сценариев синхронизации DWH.
Андреас Дитрих
7
Я думаю, что в типе db следует использовать bigint вместо целого числа, чтобы соответствовать длинному типу java.
Дмитрий
Хороший момент, Дмитрий, спасибо, хотя когда дело касается версионирования, ИМХО это не имеет особого значения.
G. Demecki 02
9

Каждый раз, когда объект обновляется в базе данных, поле версии увеличивается на единицу. Каждая операция, которая обновляет объект в базе данных, будет добавлена WHERE version = VERSION_THAT_WAS_LOADED_FROM_DATABASEк запросу.

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

stefanglase
источник
Не через JPAUpdateQuery, это не так. Таким образом, «Каждая операция, которая обновляет объект в базе данных, будет добавлять в свой запрос WHERE version = VERSION_THAT_WAS_LOADED_FROM_DATABASE» неверно.
Педро Борхес
2

Версия, используемая для обеспечения только одного обновления за раз. Поставщик JPA проверит версию, если ожидаемая версия уже увеличивается, то кто-то уже обновляет объект, поэтому будет создано исключение.

Таким образом, обновление значения объекта будет более безопасным и оптимистичным.

Если значение часто меняется, вы можете не использовать поле версии. Например, «объект, у которого есть поле счетчика, которое будет увеличиваться каждый раз при доступе к веб-странице»

uudashr
источник
0

Просто добавляю еще немного информации.

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

То же самое можно сказать и об обновлении через JPQL, то есть не о простом изменении объекта, а о команде обновления базы данных, даже если это выполняется с помощью спящего режима.

Pedro

Педро Борхес
источник
3
Что есть JPAUpdateClause?
Карл Рихтер
Хороший вопрос, простой ответ: плохой пример :) JPAUpdateClause специфичен для QueryDSL, подумайте о запуске обновления с JPQL, а не о простом влиянии на состояние объекта в коде.
Педро Борхес,