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він більше не збігатиметься з тим, що знаходиться в БД, і пункт, де заважає відбуватися оновлення. Буде оновлено рядки 0, які JPA може виявити, щоб зробити висновок про те, що відбулася одночасна модифікація.
Штійн де Вітт

Відповіді:


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, ймовірно, повинен бути ініціалізований explizitly з 0L.
Маркус

2
Не покладайтеся на те, щоб автоматично розпакувати його longабо просто зателефонувати longValue()на нього. Вам потрібні чіткі nullперевірки. Явно ініціалізуючи його самостійно, я б не робив, оскільки цим полем повинен керувати постачальник ORM. Я думаю, що він встановлюється 0Lна першу вставку в БД, тому, якщо він встановлений на nullце, ви повідомляєте, що цей запис ще не зберігається
Штійн де Вітт

Чи заважатиме це створювати суб'єкт, який (намагався) створити не раз? Я маю на увазі, припустимо, тематичне повідомлення JMS запускає створення об'єкта, і існує кілька примірників, коли програма прослуховує це повідомлення. Я просто хочу уникнути неповторної помилки порушення обмежень ..
Ману,

Вибачте, я розумію, що це стара тема, але чи @Version також враховує брудний кеш у кластерних середовищах?
Шон Ейон Сміт

3
Якщо ви використовуєте Spring Data JPA, можливо, краще використовувати обгортку, Longа не примітив longдля цього @Versionполя, тому що SimpleJpaRepository.save(entity), ймовірно, трактувати поле нульової версії буде індикатором того, що сутність є новою. Якщо сутність нова (як зазначено в нульовій версії), 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може мати сенс також). (На мій погляд, це дуже основний бізнес-мета-атрибут). Зазвичай його значення зберігається як DATE (TIME) (мається на увазі приблизно рік ... мільйон) в UTC (якщо "місцеположення" редактора часового поясу важливо зберігати тоді з часовим поясом). додатково дуже корисно для сценаріїв синхронізації DWH.
Андреас Дітріх

7
Я думаю, що bigint замість цілого числа слід використовувати в db-типі, щоб відповідати довгому типу java.
Дмитро

Добрий момент, Дмитро, дякую, хоча, коли мова йде про версію IMHO, це насправді не має значення.
Г. Демецький

9

Щоразу, коли сутність оновлюється в базі даних, поле версії збільшуватиметься на одиницю. Кожна операція, яка оновлює сутність у базі даних, буде додана WHERE version = VERSION_THAT_WAS_LOADED_FROM_DATABASEдо її запиту.

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


Не через JPAUpdateQuery, це не так. Отже, "Кожна операція, яка оновлює сутність у базі даних, додала б до її запиту WHERE версію = VERSION_THAT_WAS_LOADED_FROM_DATABASE" не відповідає дійсності.
Педро Борхес

2

Версія використовується для забезпечення лише одного оновлення за раз. Провайдер JPA перевірить версію, якщо очікувана версія вже збільшиться, тоді хтось уже оновить сутність, тому буде викинуто виняток.

Таким чином, оновлення вартості об'єкта було б більш безпечним і оптимістичним.

Якщо значення часто часте, ви можете не використовувати поле версії. Наприклад, "сутність, яка має лічильне поле, яке збільшуватиметься кожного разу, коли доступ до веб-сторінки"


0

Просто додайте трохи більше інформації.

JPA керує версією під кришкою для вас, однак це не робиться, коли ви оновлюєте свій запис за допомогою JPAUpdateClause, у таких випадках вам потрібно вручну додавати приріст версії до запиту.

Те саме можна сказати про оновлення через JPQL, тобто не просту зміну сутності, а команду оновлення бази даних, навіть якщо це робиться в сплячому режимі.

Педро


3
Що таке JPAUpdateClause?
Карл Ріхтер

Хороший запитання, проста відповідь: поганий приклад :) JPAUpdateClause - специфічний для QueryDSL, подумайте, що запускається оновлення з JPQL, а не просто впливає на стан суб'єкта господарювання в коді.
Педро Борхес
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.