3

私はJavaSEを使用しており、永続API(toplink-essentials)を使用してDerbyDBのエンティティを管理する方法について学習しています。注:これは(遠隔教育)大学の仕事ですが、この問題がコースの資料に現れる「宿題」ではありません。

同じエンティティのセットで動作する2つのスレッドがあります。私の問題は、私が試したすべての方法で、1つのスレッドのクエリ結果セット(トランザクション内で実行されるクエリ)内のエンティティを変更して、結果セットが残りのトランザクションで無効になるようにすることができることです。

たとえば、1つのスレッドからこの操作が実行されます。

static void updatePrices(EntityManager manager, double percentage) {
    EntityTransaction transaction = manager.getTransaction();

    transaction.begin();
    Query query = manager.createQuery("SELECT i FROM Instrument i where i.sold = 'no'");
    List<Instrument> results = (List<Instrument>) query.getResultList();

    // force thread interruption here (testing non-repeatable read)
    try { Thread.sleep(2000); } catch (Exception e) { }

    for (Instrument i : results) {
        i.updatePrice(percentage);
    }
    transaction.commit();
    System.out.println("Price update commited");
}

そして、このメソッドで別のスレッドから中断された場合:

private static void sellInstrument(EntityManager manager, int id)
{
    EntityTransaction transaction = manager.getTransaction();
    transaction.begin();
    Instrument instrument = manager.find(Instrument.class, id);
    System.out.println("Selling: " + instrument.toFullString());
    instrument.setSold(true);
    transaction.commit();
    System.out.println("Instrument sale commited");
}

発生する可能性があるのは、スレッド内のスレッドがupdatePrices()再開すると、クエリresultSetが無効になり、販売されたアイテムの価格が、販売されたときとは異なる価格に更新されることです。(ショップは、DBで販売されたアイテムの記録を保持したいと考えています)。同時トランザクションが発生しているEntityManagerため、(同じファクトリからの)スレッドごとに異なるものを使用しています。

(中断された)トランザクション中にクエリの結果が「無効」になるのを防ぐことは(ロックまたはある種のコンテキスト伝播によって)可能ですか?この種のシナリオがJavaEEの目的であると私は考えていますが、私が知りたいのは、JavaSEで実行できるかどうかです。


編集:

VineetとPascalのアドバイスに従う:@Versionエンティティのクラスでアノテーションを使用すると(追加のDB列を使用)、大きなトランザクション(updatePrices())が。で失敗しOptimisticLockExceptionます。ただし、大量のクエリ結果の最後に発生した場合、これは非常にコストがかかります。クエリ(内部updatePrices())に関連する行をロックさせて、内部のスレッドをsellInstrument()ブロックまたは中止して例外をスローする(その後中止する)方法はありますか?これははるかに安いでしょう。(私が理解していることから、Toplink Essentialsには悲観的なロックはありません)。

4

2 に答える 2

3

スレッドセーフ

私はあなたがあなたを管理する方法について疑問を持っていますEntityManager。aEntityManagerFactoryはスレッドセーフですが(アプリケーションの起動時に1回作成する必要があります)、そうでEntityManagerはなく、通常はEntityManagerスレッドごとに1つ使用する必要があります(またはアクセスを同期しますが、スレッドごとに1つ使用します)。

並行性

JPA 1.0は、(属性を使用する場合)楽観的ロックVersionと2つのロックモードをサポートしており、 APIを介したダーティリード繰り返し不可能なリードを回避できます。詳細については、読み取りおよび書き込みロックおよび/またはセクション3.4JPA1.0仕様のオプティミスティックロックと同時実行EntityManager.lock()性全体を読むことをお勧めします。

PS:ペシミスティックロックはJPA 1.0ではサポートされていないか、プロバイダー固有の拡張機能を介してのみサポートされていることに注意してください(JPA 2.0およびその他のロックオプションに追加されています)。念のため、Toplinkはeclipselink.pessimistic-lockクエリヒントを通じてそれをサポートしています。


JPA wikiに書かれているように、TopLink Essentialsは、クエリヒントを介してJPA1.0の悲観的ロックをサポートすることになっています。

// eclipselink.pessimistic-lock
Query Query = em.createQuery("select f from Foo f where f.bar=:bar");
query.setParameter("bar", "foobar");
query.setHint("eclipselink.pessimistic-lock", "Lock");
query.getResultList();

TopLinkを使用していないため、このヒントがすべてのバージョンでサポートされていることを確認できません。そうでない場合、「FOR UPDATE」を生成する場合は、ネイティブSQLクエリを使用する必要があります。

于 2010-07-14T23:51:38.980 に答える
2

EntityManager.lock()メソッドを確認することをお勧めします。このメソッドを使用すると、トランザクションが初期化されると、エンティティに対して楽観的または悲観的なロックを取得できます。

問題の説明に従って、データベースから「選択」されたデータベースレコードをロックしたいとします。これは、悲観的なロックを介して実現できます。これは、SELECT ... FROM tblFORUPDATEステートメントとほぼ同等です。

于 2010-07-14T23:25:50.990 に答える