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 может убедиться, что не было одновременных изменений между загрузкой и сохранением вашей сущности, потому что запрос не найдет вашу сущность в базе данных, когда номер версии был увеличен между загрузкой и сохранением.


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

2

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

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

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


0

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

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

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

Pedro


3
Что есть JPAUpdateClause?
— Карл Рихтер

Хороший вопрос, простой ответ: плохой пример :) JPAUpdateClause специфичен для QueryDSL, подумайте о запуске обновления с JPQL, а не о простом влиянии на состояние объекта в коде.
— Педро Борхес,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.