Мне нужно сгенерировать уникальный номер счета-фактуры в транзакции XG, которая включает следующие 3 группы сущностей в моей модели данных:
(верхний уровень) ContactRoot ‹-- (предок) ‹--- Контакт: контакт должен быть обновлен до статуса Клиент во время транзакции
(верхний уровень) CustomerSettings: содержит следующий порядковый номер для использования; существует один и только один экземпляр CustomerSettings с фиксированным статическим идентификатором; порядковый номер должен быть увеличен на +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: ожидается:‹[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, 3, 3, 3, 3, 3]>
Я уже пару раз просмотрел документы, не могу понять, почему это не работает?
Если вы получаете доступ к более чем одной группе сущностей в транзакции, транзакция будет транзакцией XG. Если вы делаете доступ только к одному, это не так. Стандартный лимит в 5 EG применяется ко всем транзакциям. документация по транзакциям
Что я делаю неправильно ?