2010-06-07 3 views
8

Я в середине меняю свой JPA-код, чтобы использовать потоки. У меня есть отдельный менеджер сущностей и транзакция для каждого потока.JPA атомный запрос/сохранение для многопоточного приложения

То, что я имел обыкновение иметь (для однотридовой среды) был такой код:

// get object from the entity manager 
X x = getObjectX(jpaQuery); 

if(x == null) 
{ 
    x = new X(); 
    x.setVariable(foo); 
    entityManager.persist(x); 
} 

С этим кодом в многопоточных среде я получаю дубликаты ключей, так как, я полагаю, getObjectX возвращает нуль для thread, то этот поток поменяется, следующий поток вызывает getObjextX, также получая нуль, а затем оба потока создадут и сохраняют новый X().

Коротких добавлений в синхронизации, есть атомный способ получить/сохранить, если-не-существует значение с JPA или я должен пересмотреть свой подход

EDIT:

Я использую последняя EclipseLink и MySql 5,1

EDIT 2:

Я добавил синхронизацию ... MASSIVE падение производительности (до такой степени, что она не может быть использована). Собираясь собирать все данные вверх по основному потоку, а затем делать творения в этом потоке.

ответ

4

Короткий грустный ответ: API JPA не может этого сделать для вас. Весь API более или менее построен вокруг оптимистического принципа, предполагающего, что вещи будут работать и бросать исключения в случае одновременной модификации.

Если это происходит часто, возможно, существует какой-то другой компонент (независимо от того, что генерирует foo?), Что может быть полезно для создания потокобезопасности, возможно, альтернативы синхронизации вокруг запроса + create.

+0

Я должен сделать это в обратном порядке, что создает Foo из-под контроля, и в значительной степени гарантировано наличие дубликатов. Мне нужно собрать все данные из потоков, а затем сохранить их все в одном потоке. – TofuBeer

2

Я думаю, вам нужно будет добавить уникальное ограничение на поля, которые используются в «jpaQuery», чтобы база данных не могла создавать повторяющиеся строки с теми же критериями, которые использовались в противопоказаниях для этого запроса. Вызывающему коду необходимо уловить результирующее исключение, возникающее в результате нарушения ограничения (в идеале это будет исключение EntityExistsException, но спецификация в этом случае не ясна).

+0

У меня есть уникальные ограничения, и я получаю исключение. Я надеялся найти способ сделать это, не создавая исключений ... – TofuBeer

+1

Использование исключения, пожалуй, самое простое решение в этом сценарии, похоже на оптимистичную блокировку, которая также использует исключение при сбое. –

0

Вы уверены, что вам нужны несколько сущ. В подобной ситуации, я просто использовать один EntityManager и простые объекты блокировки за метод:

private Object customerLock = new Object[0]; 

public Customer createCustomer(){ 
    Customer customer = new Customer(); 
    synchronized(customerLock){ 
     entityManager.persist(customer); 
    } 
    return customer; 
} 

Edit: ОК, не может сделать много о производительности, за исключением сказать, что он выполняет хорошо в моих приложениях, но для уникальности используйте что-то вроде этого:

public Customer getOrCreateCustomer(String firstName, String lastName){ 
    synchronized(customerLock){ 
     List<Customer> customers = 
      entityManager.createQuery(
       "select c from Customer c where c.firstName = :firstName" 
       + " and c.lastName = :lastName" 
      ) 
      .setParam("firstName", firstName) 
      .setParam("lastName", lastName) 
      .setMaxResults(1) 
      .getResultList(); 
     if(customers.isEmpty()){ 
      Customer customer = new Customer(firstName, lastName); 
      entityManager.persist(customer); 
     }else{ 
      customer = customers.get(0); 
     } 
    } 
    return customer; 
} 
+0

Мне нужно убедиться, что объект, в вашем случае, Клиент еще не существует. Я попытался добавить синхронизацию, но производительность была далеко бедной. – TofuBeer

+0

Синхронизация на Java-уровне не будет работать в кластере – Vadzim

3

Некоторые "взломать", чтобы рассмотреть следующие вопросы:

  • реализации hashCode() и equals() на основе бизнес-ключа объектов (не сгенерированная ид)
  • синхронизироваться на:

    (obj.getClass().getName() + String.valueOf(hashCode())).intern() 
    

Таким образом, вы получите блокировки только в соответствующих случаях.

+0

Хммм ... У меня уже есть такие равенства/хэш-код ... Никогда не думал о синхронизации на них. Я дам ему шанс увидеть. – TofuBeer

+0

это умно :-) –

+0

Это не сработает в кластере – Vadzim

Смежные вопросы