Исключение персистентности Java EE с условием гонки потока и атрибутом транзакции require_new - PullRequest
1 голос
/ 16 января 2012

У меня есть приложение 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, не будет объединено.

1 Ответ

2 голосов
/ 16 января 2012

У вас есть несколько проблем.

Первая проблема: persist () не вставляется в базу данных. Это только делает временную сущность прикрепленной. Вставка выполняется только при сбросе (à вызывается, явно неявно перед фиксацией транзакции. Поймать исключение, выброшенное с помощью persist, не получится

Вторая проблема: поскольку A.someRESTCall и B.buildItem выполняются в одной и той же транзакции, если JPA выдает какое-либо исключение, единственное, что вы можете сделать, - откатить транзакцию и отменить сеанс. Сеанс Hibernate находится в нестабильном состоянии после возникновения любого исключения, и вы не можете восстановить ни одно исключение, которое оно выдает.

Итак, что бы я сделал:

  • получить предмет из базы данных
  • если он есть, обновите его (нет необходимости вызывать слияние здесь: объект присоединен) и верните
  • если его нет, вызовите метод с транзакцией require_new, чтобы создать и обновить элемент
  • если этот метод завершится успешно, вернитесь.
  • если этот метод вызывает исключение, вернитесь к шагу 1.
...