У меня есть приложение Java EE, к которому подключены веб-службы REST. Каждый вызов REST получает свой собственный компонент без сохранения состояния. Этот компонент вызывает вспомогательный компонент для создания новых элементов и сохранения их в базе данных. Когда компонент REST возвращает элемент, немного манипулирует им, а затем объединяет его с базой данных. Вот общий поток
// Bean A
void someRESTCall()
{
Item i = beanB.getItem(...);
// Possible race condition here
if(i == null)
{
i = beanB.buildItem();
if(i == null)
{
// Assume race condition
i = beanB.getItemForce(...); // This has transactional attribute REQUIRES_NEW
}
}
// Do some stuff to the item and merge it
i.setColor("blue");
entityManager.merge(i);
}
// Bean B
Item buildItem(...)
{
Item i = new Item();
i.setName(name); // Name is the primary key, cannot be changed.
try
{
entityManager.persist(i);
}
catch(PersistenceException ex)
{
// Assume threading issue / race condition
i = null;
}
return i;
}
Первоначальная проблема, с которой мы столкнулись, заключалась в том, что если два вызова rest попадают в состояние гонки, вы можете иметь дубликаты в базе данных (что приводит к исключению ограничения, поскольку первичный ключ не генерируется автоматически).
Итак, чтобы обойти это, я добавил возвращаемое значение null в buildMachine. Проблема состоит в том, что транзакция откатывается, и, хотя A продолжает выполняться, он не может правильно обновить базу данных после отката.
Итак, чтобы обойти эту проблему, я попытался добавить атрибут транзакции REQUIRES_NEW
в метод buildMachine. Когда я это делаю, я получаю исключение ограничения, когда beanA переходит к слиянию, и в него вносятся изменения.
Итак, как мне обойти как условие гонки, так и исключение ограничения? Возможно, еще важнее, почему вызов слияния приводит к исключению ограничения? Следует повторно использовать существующую строку базы данных элементов, возвращаемую buildItem
Изменение базы данных для разрешения автоматически сгенерированных первичных ключей не допускается.
Некоторые люди предложили убрать вызов на entityManager.merge()
в beanA, сказав, что он / был избыточен. Если я сделаю это, ни одно из изменений, сделанных A, не будет объединено.