データベースに永続化された Ticket オブジェクトのキューがあります。キューを含むエンティティは次のようになります。
@Entity
public class StoreQueueCollection {
@Id
private int storeId;
@ManyToMany(fetch=FetchType.LAZY)
@OrderColumn
private List<Ticket> mainQueue = new ArrayList<Ticket>();
@ManyToMany(fetch=FetchType.LAZY)
@OrderColumn
private List<Ticket> cancelledQueue = new ArrayList<Ticket>();
.. etc
あるキューから別のキューにチケットを移動する操作 (これを changeStatus と呼びます) と、新しいチケットをキューの最後に追加する操作 (この newTicket と呼びます) があります。
2 つの操作が同じキューでインターリーブする場合、操作は基本的に機能しますが、キューに「ギャップ」が生じます。データベースでは、これは、0、1、2、4 のように、テーブルの順序列に欠落したインデックスのように見えます。キューが Java にロードされると、欠落したインデックスはキュー内の null 要素になります。
StoreQueueCollection オブジェクトでペシミスティック ロックを使用して、一貫性のないインターリーブ更新を防止していますが、期待どおりに機能していません。追加のログを使用すると、次のような奇妙なシーケンスが表示されます。
- changeTicketStatus() 開始 - entityManager.refresh() を使用してキューをロックします - キュー A の先頭からチケット X が削除されました - キューは entityManager.flush()ed です * newTicket() 開始 * 新しいチケット Y を作成し、entityManager.refresh() を使用して StoreQueueCollection をロックします。 * キュー A の最後にチケット Y が追加されました * チケット Y には初期化されたフィールドがあり、save()d です * refresh() を呼び出すと、メソッドがブロックされます - changeTicketStatus() 再開 (メモリ内キューを印刷すると、チケット X がキュー A にないことが示されます) - チケット X が別のキュー B に追加されました - チケット X の一部のフィールドが変更されました - チケット X は repository.save() を使用して保存されます (メモリ内キューを印刷すると、チケット X がキュー A にないことが示されます) - changeTicketStatus() 完了 * newTicket() 再開 * refresh() が返す (メモリ内キュー A を印刷すると、チケット X がまだキューにあることがわかります!) * チケット Y はキュー A の最後に追加されます (メモリ内キュー A を印刷すると、チケット X と Y がキューにあることがわかります) * キューは save()d です
すべてのロックは LockModeType.PESSIMISTIC_WRITE であり、スコープは PessimisticLockScope.EXTENDED です。
この一連の実行の後、キュー内の null エントリをチェックする別のスレッドからアサーションがトリガーされます。不思議なことに、行列は基本的に正しい(Xを削除、Yを末尾に追加)のですが、Yの前の注文欄に隙間があります。
私たちが間違っていることについての提案は大歓迎です!