Java-JPA-@Versionアノテーション


118

@VersionJPAでアノテーションはどのように機能しますか?

私は次のように抽出されたさまざまな答えを見つけました:

JPAはエンティティのバージョンフィールドを使用して、同じデータストアレコードに対する同時変更を検出します。JPAランタイムは、同じレコードを同時に変更する試みを検出すると、最後にコミットしようとしたトランザクションに例外をスローします。

しかし、それがどのように機能するかはまだわかりません。


次の行からも:

バージョンフィールドは不変と見なす必要があります。フィールド値を変更すると、未定義の結果が生じます。

バージョンフィールドを次のように宣言する必要があるという意味finalですか?


1
すべての更新クエリでバージョンを確認/更新するだけですUPDATE myentity SET mycolumn = 'new value', version = version + 1 WHERE version = [old.version]。誰かがレコードを更新した場合old.version、DB のレコードと一致しなくなり、where句によって更新が行われなくなります。「更新された行」はになります0。JPAはこれを検出して、同時変更が発生したと結論付けることができます。
Stijn de Witt 2017

回答:


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ます。

バージョンフィールドをfinalとして宣言する必要があるという意味ですか

いいえ、ただし、セッターを呼び出す必要がないため、セッターを保護することを検討できます。


5
Longはnullとして初期化されるため、このコードの一部でnullポインター例外が発生しました。おそらく、0Lで明示的に初期化する必要があります。
マーカス

2
自動でボックス化解除しlongたり、呼び出したりするだけに頼らないでくださいlongValue()。明示的なnullチェックが必要です。このフィールドはORMプロバイダーによって管理されることになっているため、自分で明示的に初期化することはしません。0LDBへの最初の挿入時に設定されると思うので、これに設定されnullている場合、このレコードはまだ永続化されていないことがわかります。
Stijn de Witt

これにより、エンティティが2回以上作成(試行)されるのを防ぐことができますか?つまり、JMSトピックメッセージがエンティティの作成をトリガーし、アプリケーションがメッセージをリッスンする複数のインスタンスがあるとします。私はちょうど..ユニーク制約違反エラーを回避したい
マヌー

申し訳ありませんが、これは古いスレッドであることは承知していますが、@ Versionはクラスタ環境でのダーティキャッシュも考慮しますか?
Shawn Eion Smith 2016

3
Spring Data JPAを使用するLong場合、nullバージョンのフィールドはエンティティが新しいことを示すインジケータとして扱われる可能性が高いため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. アノテーションが付けられたフィールドが誤ってに設定されている場合、オプティミスティックロックは機能しません@Versionnull
  2. この特別なフィールドが誤解を避けるために、必ずしもオブジェクトのビジネス版ではないので、私のようなものに、このようなフィールドに名前を付けることを好むoptlockのではなくversion

JPAベンダーは作成時にフィールドを強制するため、アプリケーションがデータベースにデータを挿入するためにJPA のみを使用する場合、最初のポイントは問題ではありません。しかし、ほとんどの場合、プレーンSQLステートメントも使用されています(少なくとも単体テストと統合テストの間)。0@version


私はそれをu_lmod(最後に変更されたユーザー)と呼び、ユーザーは人間または定義済みの(自動化された)プロセス/エンティティ/アプリです(そのため、追加の保存u_lmod_idも意味があります)。(それは私の見解では非常に基本的なビジネスメタ属性です)。通常は、UTCでDATE(TIME)(年に関する情報を意味する...)として値を格納します(エディターのタイムゾーン「場所」がタイムゾーンで格納することが重要な場合)。さらに、DWH同期シナリオに非常に役立ちます。
アンドレアスディートリッヒ

7
整数の代わりにbigintをdbタイプで使用して、長いJavaタイプと一致させる必要があると思います。
ドミトリー

良い点ドミトリー、ありがとう。ただし、IMHOのバージョン管理に関しては、それほど重要ではありません。
G.デメッキ2017

9

エンティティがデータベースで更新されるたびに、バージョンフィールドが1つずつ増加します。データベース内のエンティティを更新するすべての操作はWHERE version = VERSION_THAT_WAS_LOADED_FROM_DATABASE、クエリに追加されます。

操作の影響を受ける行をチェックするときに、JPAフレームワークは、エンティティのロードと永続化の同時変更がないことを確認できます。ロードと永続化の間でバージョン番号が増加すると、クエリがデータベースでエンティティを見つけられないためです。


JPAUpdateQuery経由ではありません。したがって、「データベース内のエンティティを更新するすべての操作で、WHERE version = VERSION_THAT_WAS_LOADED_FROM_DATABASEがクエリに追加されます」は当てはまりません。
ペドロボルヘス

2

一度に1つの更新のみを確実にするために使用されるバージョン。JPAプロバイダーはバージョンをチェックします。予想されるバージョンがすでに増加している場合は、誰かがすでにエンティティを更新しているため、例外がスローされます。

したがって、エンティティ値の更新は、より安全で、より楽観的になります。

値が頻繁に変わる場合は、バージョンフィールドを使用しないことを検討してください。たとえば、「カウンタフィールドを持つエンティティ。Webページにアクセスするたびに増加します」


0

もう少し情報を追加するだけです。

JPAは内部でバージョンを管理しますが、を使用してレコードを更新する場合は管理しません。そのJPAUpdateClauseような場合は、手動でバージョンの増分をクエリに追加する必要があります。

JPQLによる更新についても同じことが言えます。つまり、エンティティへの単純な変更ではなく、データベースへの更新コマンドがhibernateによって実行された場合でも同様です。

ペドロ


3
なにJPAUpdateClause
カールリヒター

良い質問、簡単な答えは次のとおりです。悪い例:) JPAUpdateClauseはQueryDSL固有です。コード内のエンティティの状態に単に影響を与えるのではなく、JPQLで更新を実行することを考えてください。
ペドロボルヘス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.