には、タイムスタンプフィールドがあり、複雑な識別子フィールドによって区別されるJPAエンティティがあります。必要なのは、すでに保存されているエンティティのタイムスタンプを更新することです。それ以外の場合は、現在のタイムスタンプで新しいエンティティを作成して保存します。
結局のところ、タスクは一見したところほど単純ではありません。問題は、並行環境で厄介な「一意のインデックスまたは主キー違反」例外が発生することです。これが私のコードです:
// Load existing entity, if any.
Entity e = entityManager.find(Entity.class, id);
if (e == null) {
// Could not find entity with the specified id in the database, so create new one.
e = entityManager.merge(new Entity(id));
}
// Set current time...
e.setTimestamp(new Date());
// ...and finally save entity.
entityManager.flush();
この例では、エンティティ識別子は挿入時に生成されないことに注意してください。事前にわかっています。
2つ以上のスレッドがこのコードブロックを並行して実行すると、メソッド呼び出しnull
から同時に取得する可能性があるentityManager.find(Entity.class, id)
ため、2つ以上のエンティティを同時に保存しようとし、同じ識別子でエラーが発生します。
この問題の解決策はほとんどないと思います。
- 確かに、このコードブロックをグローバルロックと同期して、データベースへの同時アクセスを防ぐことはできますが、それが最も効率的な方法でしょうか?
MERGE
一部のデータベースは、既存の行を更新するか、存在しない場合は新しい行を作成する非常に便利なステートメントをサポートしています。しかし、OpenJPA(私の選択したJPA実装)がそれをサポートしているとは思えません。- イベントJPAがSQLMERGEをサポートしていない場合、いつでも古いJDBCにフォールバックして、データベースでやりたいことが何でもできます。しかし、私は快適なAPIを残したくなく、毛むくじゃらのJDBC+SQLの組み合わせを台無しにしたくありません。
- 標準のJPAAPIのみを使用して修正するための魔法のトリックがありますが、私はまだそれを知りません。
助けてください。