Java - JPA - adnotacja @Version

118

Jak działa @Versionadnotacja w JPA?

Znalazłem różne odpowiedzi, których wyciąg jest następujący:

JPA używa pola wersji w jednostkach, aby wykrywać współbieżne modyfikacje tego samego rekordu magazynu danych. Gdy środowisko wykonawcze JPA wykryje próbę równoczesnej modyfikacji tego samego rekordu, zgłasza wyjątek od transakcji, która próbuje zatwierdzić jako ostatnia.

Ale nadal nie jestem pewien, jak to działa.


Również z następujących linii:

Należy rozważyć niezmienność pól wersji. Zmiana wartości pola ma niezdefiniowane wyniki.

Czy to oznacza, że ​​powinniśmy zadeklarować nasze pole wersji jako final?

Yatendra Goel
źródło
1
Wszystko robi to check / update wersja w każdym zapytaniu aktualizacja: UPDATE myentity SET mycolumn = 'new value', version = version + 1 WHERE version = [old.version]. Jeśli ktoś zaktualizował rekord, old.versionnie będzie już pasował do tego w bazie danych, a klauzula where uniemożliwi aktualizację. Będą to „zaktualizowane wiersze” 0, które JPA może wykryć, aby stwierdzić, że nastąpiła równoległa modyfikacja.
Stijn de Witt

Odpowiedzi:

189

Ale nadal nie jestem pewien, jak to działa?

Powiedzmy, że encja MyEntityma opisaną versionwłaściwość:

@Entity
public class MyEntity implements Serializable {    

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @Version
    private Long version;

    //...
}

Po aktualizacji pole z adnotacją @Versionzostanie zwiększone i dodane do WHEREklauzuli, coś takiego:

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

Jeśli WHEREklauzula nie pasuje do rekordu (ponieważ ta sama jednostka została już zaktualizowana przez inny wątek), dostawca trwałości wyrzuci plik OptimisticLockException.

Czy to oznacza, że ​​powinniśmy zadeklarować nasze pole wersji jako ostateczne

Nie, ale możesz rozważyć zabezpieczenie rozgrywającego, ponieważ nie powinieneś tego nazywać.

Pascal Thivent
źródło
5
Otrzymałem wyjątek wskaźnika null w tym fragmencie kodu, ponieważ Long inicjalizuje się jako null, prawdopodobnie powinien być zainicjowany jawnie z 0L.
Markus,
2
Nie polegaj na automatycznym rozpakowywaniu longlub po prostu dzwonieniu longValue()do niego. Potrzebujesz wyraźnych nullczeków. Jawne zainicjowanie go samodzielnie Nie zrobiłbym tego, ponieważ to pole powinno być zarządzane przez dostawcę ORM. Myślę, że zostanie ustawiony na 0Lprzy pierwszym wstawieniu do bazy danych, więc jeśli jest ustawiony na nullto, oznacza to, że ten rekord nie został jeszcze utrwalony.
Stijn de Witt
Czy zapobiegnie to tworzeniu (próbowaniu) tworzenia bytu więcej niż jeden raz? Chodzi mi o to, że przypuśćmy, że komunikat tematu JMS wyzwala utworzenie jednostki i istnieje wiele instancji aplikacji nasłuchujących komunikatu. Chcę tylko uniknąć unikalnego błędu naruszenia ograniczenia ...
Manu
Przepraszam, zdaję sobie sprawę, że jest to stary wątek, ale czy @Version uwzględnia również brudną pamięć podręczną w środowiskach klastrowych?
Shawn Eion Smith
3
Jeśli używasz Spring Data JPA, może być lepiej użyć opakowania Longniż prymitywu longdla @Versionpola, ponieważ SimpleJpaRepository.save(entity)prawdopodobnie potraktuje pole wersji zerowej jako wskaźnik, że jednostka jest nowa. Jeśli jednostka jest nowa (co wskazuje wersja jest pusta), Spring wywoła em.persist(entity). Ale jeśli wersja ma wartość, to zadzwoni em.merge(entity). Z tego powodu pole wersji zadeklarowane jako typ opakowania powinno pozostać niezainicjowane.
rdguam
33

Chociaż odpowiedź @Pascal jest całkowicie poprawna, z mojego doświadczenia uważam, że poniższy kod jest pomocny w optymistycznym blokowaniu:

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

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

    // ...
}

Czemu? Ponieważ:

  1. Optymistyczne blokowanie nie zadziała, jeśli pole z adnotacją @Versionjest przypadkowo ustawione na null.
  2. Ponieważ to specjalne pole niekoniecznie jest wersją biznesową obiektu, aby uniknąć wprowadzenia w błąd, wolę nazwać takie pole optlockraczej niż version.

Pierwszy punkt nie ma znaczenia, jeśli aplikacja używa tylko JPA do wstawiania danych do bazy danych, ponieważ dostawca JPA będzie wymuszał 0dla @versionpola w czasie tworzenia. Ale prawie zawsze używane są również proste instrukcje SQL (przynajmniej podczas testów jednostkowych i integracyjnych).

G. Demecki
źródło
Nazywam to u_lmod(ostatnia modyfikacja użytkownika), gdzie użytkownik może być człowiekiem lub zdefiniowanym (zautomatyzowanym) procesem / jednostką / aplikacją (więc dodatkowe przechowywanie również u_lmod_idmoże mieć sens). (Moim zdaniem jest to bardzo podstawowy biznesowy meta-atrybut). Zwykle przechowuje swoją wartość jako DATA (CZAS) (co oznacza informacje o roku ... milisach) w UTC (jeśli „lokalizacja” strefy czasowej edytora jest ważna do przechowywania, to wraz ze strefą czasową). dodatkowo bardzo przydatne w scenariuszach synchronizacji DWH.
Andreas Dietrich,
7
Myślę, że bigint zamiast liczby całkowitej powinien być używany w typie db, aby dopasować długi typ java.
Dmitry
Słuszna uwaga Dmitry, dzięki, chociaż jeśli chodzi o wersjonowanie IMHO, to tak naprawdę nie ma to znaczenia.
G. Demecki
9

Za każdym razem, gdy jednostka jest aktualizowana w bazie danych, pole wersji zostanie zwiększone o jeden. Każda operacja aktualizująca jednostkę w bazie danych zostanie dołączona WHERE version = VERSION_THAT_WAS_LOADED_FROM_DATABASEdo jej zapytania.

Sprawdzając wiersze operacji, których dotyczy problem, struktura jpa może upewnić się, że nie nastąpiła równoczesna modyfikacja między ładowaniem a utrwalaniem jednostki, ponieważ zapytanie nie znajdzie jednostki w bazie danych, gdy jej numer wersji został zwiększony między ładowaniem a utrwalaniem.

stefanglase
źródło
Nie przez JPAUpdateQuery. Zatem „Każda operacja, która aktualizuje jednostkę w bazie danych, będzie zawierać WHERE version = VERSION_THAT_WAS_LOADED_FROM_DATABASE” nie jest prawdą.
Pedro Borges
2

Wersja używana w celu zapewnienia, że ​​tylko jedna aktualizacja na raz. Dostawca JPA sprawdzi wersję, jeśli oczekiwana wersja już się zwiększy, to ktoś inny już zaktualizuje jednostkę, więc zostanie wyrzucony wyjątek.

Tak więc aktualizacja wartości jednostki byłaby bezpieczniejsza i bardziej optymistyczna.

Jeśli wartość zmienia się często, możesz rozważyć nieużywanie pola wersji. Na przykład „jednostka, która ma pole licznika, które będzie się zwiększać za każdym razem, gdy otwierana jest strona internetowa”

uudashr
źródło
0

Dodam trochę więcej informacji.

JPA zarządza wersją pod maską za Ciebie, jednak nie robi tego, gdy aktualizujesz rekord za pośrednictwem JPAUpdateClause, w takich przypadkach musisz ręcznie dodać przyrost wersji do zapytania.

To samo można powiedzieć o aktualizacji przez JPQL, tj. Nie jest to prosta zmiana encji, ale polecenie aktualizacji do bazy danych, nawet jeśli odbywa się to przez hibernację

Pedro

Pedro Borges
źródło
3
Co to jest JPAUpdateClause?
Karl Richter
Dobre pytanie, prosta odpowiedź to: zły przykład :) JPAUpdateClause jest specyficzne dla QueryDSL, pomyśl o uruchomieniu aktualizacji za pomocą JPQL, a nie po prostu wpływać na stan jednostki w kodzie.
Pedro Borges