2016-11-03 8 views
1

MySQLでHibernateを介して競合する同時データベースの挿入に関する問題を解決しようとしています。保護されたブロックを使用してHibernateの挿入を同期する

私は同時に複数のスレッドで簡単に実行できるコードを用意しています。レコードの存在をデータベースでチェックしており、存在しない場合は新しいレコードが挿入されます。これは、関連する子レコードに対して同じ挿入操作が存在する場合に実行されます。 2つのスレッドが同時に子レコードを永続化しようとすると、ConstraintViolationExceptionが発生します。これは、両方のスレッドが、クエリを実行しているときにレコードが存在しないことを両方のスレッドが一意制約に違反する、そのうちの1つが失敗します。

保護されたブロックを使用してアプリケーションレベルでクエリ挿入操作を同期しようとしているため、データベースにクエリを実行する前に別のスレッドがレコードの挿入を完了するのをスレッドが待機しています。しかし、同期が動作しているのを見ても、レコードが別のスレッドで永続化されていても、レコードのクエリは結果を返しません。したがって、依然として制約違反が発生します。

  • 私は、グローバルクエリキャッシュと二次キャッシュを有効にしているが、SELECTは
  • を照会するためにCacheMode.REFRESHを使用しています手動
  • データベーストランザクションを管理していますHibernateは5.1.0
  • を使用しています私は楽観的または悲観的なデータベースのロックや行のバージョン管理を使用していません。ここで

コードの例です:私はそれが存在しない場合、製品を持続しようとする各同期動作で

、および関連する親サプライヤーが存在しない場合。同時に要求を行った後

public class UpdateProcessor extends HttpServlet { 

    // Indicator used for synchronizing read-insert operations 
    public static Boolean newInsertInProgress = false; 

    @Override 
    public void doPost(HttpServletRequest request, HttpServletResponse response) { 

    Session hbSession = null; 
    Transaction tx = null; 
    try { 
     hbSession = HibernateUtils.getNewSession(); 

     UpdateProcessor.waitForInsert(); // if there is an insert in progress, wait for it to finish 
     UpdateProcessor.notifyInsertStarted(); // obtain lock 

     tx = hbSession.beginTransaction(); 

     Product existingProduct = findProductBySKU(sku); 
     if(existingProduct == null) { 

     Product newProduct = new Product(); 
     newProduct.setSKU(sku); 

     Supplier existingSupplier = findSupplierByName(name); 
     if(existingSupplier == null) { 
      Supplier newSupplier = new Supplier(); 
      newSupplier.setName(name); 
      db.save(newSupplier); 
      newProduct.setSupplier(newSupplier); 
     } else { 
      newProduct.setSupplier(existingSupplier); 
     } 

     db.save(newProduct); 
     } 

     tx.commit(); 

    } catch (Exception t) { 
     // <rollback transaction> 
     response.sendError(500); 
    } finally { 

     // Safeguard to avoid thread deadlock - release lock always, if obtained 
     if(UpdateProcessor.newInsertInProgress) { 
      UpdateProcessor.notifyInsertFinished(); // release lock and notify next thread 
     } 

     // <close session> 
    } 
    } 

    private static synchronized void waitForInsert() { 
    if(!UpdateProcessor.newInsertInProgress) { 
     log("Skipping wait - thread " + Thread.currentThread().getId() + " - " + System.currentTimeMillis()); 
     return; 
    } 
    while(UpdateProcessor.newInsertInProgress) { 
     boolean loggedEntering = false; 
     if(!loggedEntering) { 
      log("Entering wait - thread " + Thread.currentThread().getId() + " - " + System.currentTimeMillis()); 
      loggedEntering = true; 
     } 
     try { 
      UpdateProcessor.class.wait(); 
     } catch (InterruptedException e) {} 
    } 
    log("Exiting wait - thread " + Thread.currentThread().getId() + " - " + System.currentTimeMillis()); 
    } 

    private static synchronized void notifyInsertStarted() { 
    UpdateProcessor.newInsertInProgress = true; 
    UpdateProcessor.class.notify(); 
    log("Notify start - thread " + Thread.currentThread().getId() + " - " + System.currentTimeMillis()); 
    } 

    private static synchronized void notifyInsertFinished() { 
    UpdateProcessor.newInsertInProgress = false; 
    UpdateProcessor.class.notify(); 
    log("Notify finish - thread " + Thread.currentThread().getId() + " - " + System.currentTimeMillis()); 
    } 
} 

出力:一意制約(ID、名前)が破られるので

Skipping wait - thread 254 - 1478171162713 
Notify start - thread 254 - 1478171162713 
Entering wait - thread 255 - 1478171162713 
Entering wait - thread 256 - 1478171162849 
Notify finish - thread 254 - 1478171163050 
Exiting wait - thread 255 - 1478171163051 
Notify start - thread 255 - 1478171163051 
Entering wait - thread 256 - 1478171163051 
Error - thread 255: 
org.hibernate.exception.ConstraintViolationException: could not execute statement 
... 
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '532-supplier-name-1' for key 'supplier_name_uniq' 

新しい仕入先レコードを永続化することは、まだスレッド255で例外をスローしました。

同期挿入後にSELECTがまだレコードを返さないのはなぜですか?マルチインサートの問題を回避するには、ガードされたロックが正しい方法ですか?

短い答え:上記Mechkovの答えに基づいて

+1

保存後にHibernateセッションをフラッシュしましたか? hSession.flush() – Mechkov

+0

はい、手動でフラッシュしても例外がスローされます。トランザクションのコミット(tx.commit())もまた、バックグラウンドの背後にあります。 –

+0

完了するために、キャッシュとすべてのキャッシュを無効にして試すことができますか? – Mechkov

答えて

0

私はコードの同期枚でHibernateセッションの作成を含める必要がありました。

長い答えは: 保護されたブロックが正常にクエリの挿入同期ブロックが、問題は、1つのスレッドが、レコードを永続finishes even though fresh Hibernateのセッションが作成されるまで、第二のスレッドがデータベースに変更を見ることができないということでした。したがって、同時のデータベース変更の影響は、すべてのスレッドですぐには認識されません。最新のデータベース状態は、他のスレッドで挿入が行われた後にセッションを作成することによって取得されます。同期されたコードにセッションの作成を含めることはそうであることを保証します。

関連する問題