我必須產生在一個XG交易,其包括在我的數據模型以下3實體組一個唯一的發票號:XG事務隔離失敗(Objectify4)
(頂層)ContactRoot < - (祖先)< --- 聯繫:接觸必須與交易
(頂層)Custome期間更新到客戶端的狀態r設置:保存下一個要使用的序號; CustomerSettings中有且僅有一個實例具有固定的靜態ID;序列號必須在交易過程中增加+1
(頂層)InvoiceRoot < - (祖先)< --- 發票:分配基於在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]> but was:<[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, 3, 3, 3, 3, 3]>
我通過文檔已經去了幾次,想不通這是爲什麼不工作?
If you access more than one entity group in a transaction, the transaction with be an XG transaction. If you do access only one, it is not. The standard limit of 5 EGs applies to all transactions. objectify transactions documentation
我做錯了什麼?
在google group中爲物品添加了更多信息:https://groups.google.com/forum/?fromgroups=#!topic/objectify-appengine/7RWkC4DX6E0 – koma
是否有任何理由不使用數據存儲區生成下一個發票號碼作爲實體ID?你有一些要求的ID是「舊ID + 1」?如果你可以放鬆這個要求,你可以完全擺脫#generateSequence。 – sappenin
是的,他們是發票號碼,法律要求是順序的; – koma