データモデルに次の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の標準制限は、すべてのトランザクションに適用されます。 トランザクションドキュメントを客観化する
私は何を間違っているのですか?