Java - JPA - annotazione @Version


118

Come funziona l' @Versionannotazione in JPA?

Ho trovato varie risposte il cui estratto è il seguente:

JPA utilizza un campo versione nelle entità per rilevare modifiche simultanee allo stesso record dell'archivio dati. Quando il runtime JPA rileva un tentativo di modificare contemporaneamente lo stesso record, genera un'eccezione alla transazione che tenta di eseguire il commit per ultimo.

Ma non sono ancora sicuro di come funzioni.


Anche dalle seguenti righe:

Dovresti considerare i campi della versione immutabili. La modifica del valore del campo ha risultati indefiniti.

Significa che dovremmo dichiarare il nostro campo versione come final?


1
Tutto ciò che fa è controllare / aggiornare la versione in ogni query di aggiornamento: UPDATE myentity SET mycolumn = 'new value', version = version + 1 WHERE version = [old.version]. Se qualcuno ha aggiornato il record, old.versionnon corrisponderà più a quello nel DB e la clausola where impedirà l'aggiornamento. Le "righe aggiornate" saranno 0, che JPA può rilevare per concludere che si è verificata una modifica simultanea.
Stijn de Witt

Risposte:


189

Ma ancora non sono sicuro di come funzioni?

Supponiamo che un'entità MyEntityabbia una versionproprietà annotata :

@Entity
public class MyEntity implements Serializable {    

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @Version
    private Long version;

    //...
}

All'aggiornamento, il campo annotato con @Versionverrà incrementato e aggiunto alla WHEREclausola, in questo modo:

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

Se la WHEREclausola non riesce a trovare una corrispondenza con un record (perché la stessa entità è già stata aggiornata da un altro thread), il provider di persistenza genererà un file OptimisticLockException.

Significa che dovremmo dichiarare il nostro campo versione come finale

No, ma potresti considerare di proteggere il setter come non dovresti chiamarlo.


5
Ho ricevuto un'eccezione puntatore nullo con questo pezzo di codice, perché Long si inizializza come nullo, dovrebbe probabilmente essere inizializzato in modo esplicito con 0L.
Markus

2
Non fare affidamento sull'unboxing automatico longo semplicemente longValue()sul chiamarlo . Hai bisogno di nullcontrolli espliciti . Inizializzandolo esplicitamente da solo non lo farei poiché questo campo dovrebbe essere gestito dal provider ORM. Penso che venga impostato al 0Lprimo inserimento nel DB, quindi se è impostato su nullquesto ti dice che questo record non è stato ancora mantenuto.
Stijn de Witt

Ciò impedirà di creare un'entità che viene (tentata) creata più di una volta? Voglio dire, supponiamo che un messaggio di argomento JMS inneschi la creazione di un'entità e che ci siano più istanze di un'applicazione che ascolta il messaggio. Voglio solo evitare l'errore di violazione del vincolo univoco ..
Manu

Scusa, mi rendo conto che questo è un vecchio thread, ma @Version prende in considerazione anche la cache sporca negli ambienti cluster?
Shawn Eion Smith

3
Se si utilizza Spring Data JPA, potrebbe essere meglio utilizzare un wrapper Longpiuttosto che una primitiva longper il @Versioncampo, perché SimpleJpaRepository.save(entity)probabilmente tratterà un campo di versione nulla come un indicatore che l'entità è nuova. Se l'entità è nuova (come indicato dalla versione nulla), Spring chiamerà em.persist(entity). Ma se la versione ha un valore, chiamerà em.merge(entity). Questo è anche il motivo per cui un campo versione dichiarato come tipo wrapper dovrebbe essere lasciato non inizializzato.
rdguam

33

Sebbene la risposta di @Pascal sia perfettamente valida, dalla mia esperienza trovo il codice seguente utile per ottenere un blocco ottimistico:

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

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

    // ...
}

Perché? Perché:

  1. Il blocco ottimistico non funzionerà se il campo annotato con @Versionè impostato accidentalmente su null.
  2. Poiché questo campo speciale non è necessariamente una versione aziendale dell'oggetto, per evitare un errore fuorviante, preferisco denominare tale campo con qualcosa di simile optlockpiuttosto che version.

Primo punto non importa se usi applicativi solo APP per l'inserimento dei dati nel database, come APP vendor valere 0per @versionsettore al momento della creazione. Ma quasi sempre vengono utilizzate anche semplici istruzioni SQL (almeno durante i test di unità e di integrazione).


Io lo chiamo u_lmod(ultima modifica dell'utente) dove l' utente può essere un processo / entità / app umano o definito (automatizzato) (quindi anche l'archiviazione u_lmod_idpotrebbe avere senso). (È a mio avviso un meta-attributo aziendale molto semplice). Tipicamente memorizza il suo valore come DATE (TIME) (che significa informazioni sull'anno ... millisecondi) in UTC (se la "posizione" del fuso orario dell'editor è importante da memorizzare con il fuso orario). inoltre molto utile per gli scenari di sincronizzazione DWH.
Andreas Dietrich

7
Penso che bigint invece di un numero intero dovrebbe essere usato nel tipo db per abbinare un tipo java lungo.
Dmitry

Buon punto Dmitry, grazie, anche se quando si tratta di versionare IMHO non ha molta importanza.
G. Demecki

9

Ogni volta che un'entità viene aggiornata nel database, il campo della versione verrà aumentato di uno. Ogni operazione che aggiorna l'entità nel database verrà aggiunta WHERE version = VERSION_THAT_WAS_LOADED_FROM_DATABASEalla sua query.

Nel controllare le righe interessate della tua operazione, il framework jpa può assicurarsi che non ci siano state modifiche simultanee tra il caricamento e la persistenza dell'entità perché la query non troverà la tua entità nel database quando il suo numero di versione è stato aumentato tra caricamento e persistere.


Non tramite JPAUpdateQuery non lo fa. Quindi "Ogni operazione che aggiorna l'entità nel database avrà aggiunto WHERE version = VERSION_THAT_WAS_LOADED_FROM_DATABASE alla sua query" non è vera.
Pedro Borges

2

Versione utilizzata per garantire un solo aggiornamento alla volta. Il provider JPA controllerà la versione, se la versione prevista aumenta già, qualcun altro aggiorna già l'entità, quindi verrà generata un'eccezione.

Quindi l'aggiornamento del valore dell'entità sarebbe più sicuro, più ottimista.

Se il valore cambia frequentemente, potresti considerare di non utilizzare il campo della versione. Per un esempio "un'entità che ha un campo contatore, che aumenterà ogni volta che si accede a una pagina web"


0

Sto solo aggiungendo qualche informazione in più.

JPA gestisce la versione sotto il cofano per te, tuttavia non lo fa quando aggiorni il tuo record tramite JPAUpdateClause, in questi casi devi aggiungere manualmente l'incremento di versione alla query.

Lo stesso si può dire dell'aggiornamento tramite JPQL, cioè non una semplice modifica all'entità, ma un comando di aggiornamento al database anche se viene fatto da ibernazione

Pedro


3
Che cos'è JPAUpdateClause?
Karl Richter

Una buona domanda, una semplice risposta è: cattivo esempio :) JPAUpdateClause è specifico per QueryDSL, pensa di eseguire un aggiornamento con JPQL invece di influenzare semplicemente lo stato di un'entità nel codice.
Pedro Borges
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.