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の答えに基づいて
保存後にHibernateセッションをフラッシュしましたか? hSession.flush() – Mechkov
はい、手動でフラッシュしても例外がスローされます。トランザクションのコミット(tx.commit())もまた、バックグラウンドの背後にあります。 –
完了するために、キャッシュとすべてのキャッシュを無効にして試すことができますか? – Mechkov