Я посреди того, чтобы менять мой код JPA для использования потоков. У меня есть отдельный менеджер по объекту и транзакция для каждого потока.
То, что я раньше имел (для единственной потоковой среды) было кодом как:
// get object from the entity manager
X x = getObjectX(jpaQuery);
if(x == null)
{
x = new X();
x.setVariable(foo);
entityManager.persist(x);
}
С тем кодом в многопоточной среде я добираюсь, делают дубликаты ключа с тех пор, я принимаю, getObjectX пустой указатель возвратов для потока, затем тот поток выгружается, следующий поток называет getObjextX, также становясь пустым, и затем оба потока создадут и сохранят новое X().
За исключением добавления в синхронизации, там атомарный путь к get/save-if-doesn't-exist значение с JPA или если я заново продумал свой подход
Править:
Я использую последний Eclipselink и MySql 5.1
РЕДАКТИРОВАНИЕ 2:
Я добавил синхронизацию... КРУПНЫЙ хит производительности (до такой степени, что это не может использоваться). Попытка собрать все данные назад по основному потоку и затем сделать создания на том потоке.
Короткий печальный ответ: нет, JPA API не может сделать это за вас. Весь API более или менее построен на оптимистическом принципе предположения, что все будет работать, и выдачи исключений в случае одновременной модификации.
Если это происходит часто, вероятно, есть какой-то другой компонент (что бы ни генерировало foo?), Которому было бы полезно сделать потокобезопасным, как, возможно, альтернативу синхронизации по запросу + create.
Некоторые хитрости, которые следует рассмотреть:
hashCode ()
и equals ()
на основе бизнес-ключа объектов (не сгенерированный идентификатор) синхронизировать:
(obj.getClass (). getName () + String.valueOf (hashCode ())). intern ()
Таким образом, вы получите блокировки только в соответствующих случаях.
Я думаю, вам нужно добавить уникальное ограничение на поля, используемые в "jpaQuery", чтобы база данных не могла создавать дубликаты строк с одинаковыми критериями, используемыми в ограничениях для этого запроса. Вызывающий код должен будет отлавливать исключение, возникающее в результате нарушения ограничений (в идеале это будет EntityExistsException, но в спецификации этот случай неясен).
Вы уверены, что вам нужно несколько entitymanagers? В аналогичной ситуации я просто использую один entitymanager и простые объекты блокировки для каждого метода:
private Object customerLock = new Object[0];
public Customer createCustomer(){
Customer customer = new Customer();
synchronized(customerLock){
entityManager.persist(customer);
}
return customer;
}
Edit: OK, не могу ничего сказать о производительности, кроме того, что в моих приложениях это работает нормально, но для уникальности используйте что-то вроде этого:
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;
}