0

我必須產生在一個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

我做錯了什麼?

+0

在google group中爲物品添加了更多信息:https://groups.google.com/forum/?fromgroups=#!topic/objectify-appengine/7RWkC4DX6E0 – koma

+0

是否有任何理由不使用數據存儲區生成下一個發票號碼作爲實體ID?你有一些要求的ID是「舊ID + 1」?如果你可以放鬆這個要求,你可以完全擺脫#generateSequence。 – sappenin

+0

是的,他們是發票號碼,法律要求是順序的; – koma

回答

1

這段代碼使代碼不是事務性的:

final Objectify ofy = ObjectifyService.factory().begin().cache(true);

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

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

}

,因爲我重用客觀化實例,它是不是事務性的。爲了讓交易工作裏面的實例,則必須總是問情況是這樣的:在小組討論here

ObjectifyService.ofy()

更多信息。

查看ObjectifyService的實現,您可以看到新實例從堆棧上被壓入/彈出;

除此之外,測試用例還沒有運行..最好的測試方法大概是同時觸發http請求;