0

データモデルに次の3つのエンティティグループを含むXGトランザクションで一意の請求書番号を生成する必要があります。

  • (トップレベル)ContactRoot <-(祖先)<---連絡先:トランザクション中に連絡先をステータスクライアントに更新する必要があります

  • (トップレベル)CustomerSettings:使用する次のシーケンス番号を保持します。固定の静的IDを持つCustomerSettingsのインスタンスは1つだけです。トランザクション中にシーケンス番号を+1する必要があります

  • (トップレベル)InvoiceRoot <-(祖先)<--- Invoice:CustomerSettingsのシーケンス番号に基づいて新しい一意の請求書番号を割り当てます。

これはDAO実装の重要な部分です(無関係なビジネスルールチェックなどが削除されました):

public void saveInvoice(final Invoice invoice) throws BusinessRuleException {

    final Objectify ofy = ObjectifyService.factory().begin().cache(true);
    ofy.transact(new Work<Void>() {

        @Override
        public Void run() {
            CustomerSettings customerSettings = ofy.load()
                    .key(Key.create(CustomerSettings.class, CustomerSettings.ID)).safeGet();
            Contact contact = ofy.load().key(createContactKey(invoice.getContactId()).safeGet();
            contact.setContactType(ContactType.CLIENT);
            ofy.save().entity(contact).now();
            String invoiceNumber = generateSequence(ofy, customerSettings);
            invoice.setInvoiceNumber(invoiceNumber);
            ofy.save().entity(invoice).now();
            return null;
        }
    });
}

そして、次のシーケンス番号を生成するための簡略化されたバージョンでは、次の呼び出しのために次のシーケンス番号が増加し、CustomerSettingsをトランザクションで更新する必要があります(これは同期されていますが、実際には役に立たないと思います):

private synchronized String generateSequence(Objectify ofy, CustomerSettings settings) {
    String ret = "";
    int sequence = settings.getNextSequence();
    settings.setNextSequence(sequence + 1);
    ofy.save().entity(settings).now();
    ret = "" + sequence;
    return ret;
}

これは、可変スレッド数の単体テストがどのように見えるかです:

private void test(final int threadCount) throws InterruptedException, ExecutionException {
    final Environment currentEnvironment = ApiProxy.getCurrentEnvironment();
    Callable<String> task = new Callable<String>() {
        @Override
        public String call() {
            ApiProxy.setEnvironmentForCurrentThread(currentEnvironment);
            return generateInvoiceNumber();
        }
    };
    List<Callable<String>> tasks = Collections.nCopies(threadCount, task);
    ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
    List<Future<String>> futures = executorService.invokeAll(tasks);
    List<String> resultList = new ArrayList<String>(futures.size());
    // Check for exceptions
    for (Future<String> future : futures) {
        // Throws an exception if an exception was thrown by the task.
        resultList.add(future.get());
    }
    // Validate the IDs
    Assert.assertEquals(futures.size(), threadCount);
    List<String> expectedList = new ArrayList<String>(threadCount);
    for (long i = 1; i <= threadCount; i++) {
        expectedList.add("" + i);
    }
    Collections.sort(resultList);
    Assert.assertEquals(expectedList, resultList);
}

@SuppressWarnings("unchecked")
private String generateInvoiceNumber() {
    InvoiceDAO invoiceDAO = new InvoiceDAO();
    Invoice invoice = ... create a valid invoice
    invoiceDAO.saveInvoice(invoice);
    log.info("generated invoice number : " + invoice.getInvoiceNumber());
    return invoice.getInvoiceNumber();
}

たとえば、これを32スレッドで同時に実行すると:

@Test
public void test32() throws InterruptedException, ExecutionException {
    test(32);
}

しかし、後続のスレッドは、前のトランザクションが請求書番号のシーケンスを増やしたことを認識しません。

これが結果です:

junit.framework.AssertionFailedError:expected:<[1、2、3、4、5、6、7、8、9、10、11、12、13、14、15、16、17、18、19、20 21、22、23、24、25、26、27、28、29、30、31、32]>しかし、次のとおりでした:<[1、1、1、1、1、1、1、1、1、1、 1、1、1、2、2、2、2、2、2、2、2、2、2、2、2、2、2、2、3、3、3、3、3]>

私はすでに数回ドキュメントを調べましたが、なぜこれが機能しないのか理解できませんか?

トランザクションで複数のエンティティグループにアクセスする場合、そのトランザクションはXGトランザクションになります。1つだけにアクセスする場合は、そうではありません。5 EGの標準制限は、すべてのトランザクションに適用されます。 トランザクションドキュメントを客観化する

私は何を間違っているのですか?

4

1 に答える 1

1

このコードは、コードをトランザクションではなくします。

最終的なObjectifyofy= ObjectifyService.factory()。begin()。cache(true);

ofy.transact(new Work<Void>() {

       ....
       ofy.save().entity(settings).now();   
       .... 

}

トランザクションではないobjectifyインスタンスを再利用するためです。トランザクションWork内のインスタンスを取得するには、常に次のようにインスタンスに問い合わせる必要があります。

ObjectifyService.ofy()

詳細については、グループディスカッションをご覧ください

の実装をObjectifyService見ると、新しいインスタンスがスタックにプッシュ/ポップされていることがわかります。

それとは別に、テストケースはまだ実行されていません..テストするための最良の方法は、おそらくhttpリクエストを同時に実行することです。

于 2012-10-31T18:38:07.040 に答える