2010-06-07 15 views
8

Estoy en medio de cambiar mi código JPA para hacer uso de subprocesos. Tengo un administrador de entidades y una transacción por separado para cada hilo.consulta/guardado atómico JPA para la aplicación multiproceso

Lo que solía tener (para el entorno de un solo subproceso) era un código como:

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

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

Con ese código en el entorno multihilo que estoy recibiendo claves duplicadas, ya que, supongo, getObjectX devuelve un valor nulo para una hilo, luego ese hilo se intercambia, el siguiente hilo llama a getObjextX, también obtiene nulo, y luego ambos hilos crearán y persistirán una nueva X().

corto de añadir de forma sincronizada, ¿hay una manera de conseguir atómica/save-si-Indiferente-existir con un valor APP o debería reconsiderar mi acercamiento

EDIT:

estoy usando la última EclipseLink y MySql 5.1

EDIT 2:

que añade sincronización ... rendimiento éxito masivo (hasta el punto de que no se puede utilizar). Voy a reunir todos los datos en el hilo principal y luego hago las creaciones en ese hilo.

Respuesta

4

triste respuesta corta es no la API JPA no puede hacer eso por usted. Toda la API se basa más o menos en torno al principio optimista de suponer que las cosas van a funcionar y lanzando excepciones en el caso de una modificación simultánea.

Si esto sucede a menudo, es probable que haya algún otro componente (lo genera foo?) Que podrían beneficiarse de ser hecho multi-hilo, tal vez como una alternativa a la sincronización de la vuelta de la consulta + crear.

+0

Tengo que hacerlo en el reverso, lo que crea Foo está fuera de mi control, y está casi garantizado que tiene duplicados. Tendré que reunir todos los datos de los hilos y luego almacenarlos en un solo hilo. – TofuBeer

2

Creo que deberá agregar una restricción única en los campos que se utilizan en "jpaQuery" para que la base de datos no pueda crear filas duplicadas con los mismos criterios utilizados en las restricciones para esa consulta. El código de llamada deberá atrapar la excepción resultante que se produce como resultado de la violación de la restricción (idealmente será una EntityExistsException, pero la especificación no es clara en este caso).

+0

Tengo restricciones únicas, y estoy recibiendo la excepción. Esperaba encontrar una manera de hacerlo sin que surgiera una excepción ... – TofuBeer

+1

Usar una excepción es probablemente la solución más simple en este escenario, es similar al bloqueo optimista que también usa una excepción en caso de falla. –

0

¿Estás seguro de que necesitas varios entitymanagers? En una situación similar, sólo tiene que utilizar un EntityManager y simples objetos de bloqueo por método:

private Object customerLock = new Object[0]; 

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

Editar: OK, no puede hacer mucho acerca de sus resultados, salvo diciendo que se desempeña bien en mis aplicaciones, pero para su uso singularidad algo como esto:

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

Necesito asegurarme de que el objeto, en su caso, Cliente aún no existe. Traté de agregar sincronización, pero el rendimiento fue muy pobre. – TofuBeer

+0

La sincronización a nivel de Java no funcionará en el clúster – Vadzim

3

Algunos "Hack" a considerar:

  • implemento hashCode() y equals() basada en la clave del negocio de los objetos (no el identificador generado)
  • sincronizar en:

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

Por lo tanto, obtendrá bloqueos solo en los casos relevantes.

+0

Hmmm ... Ya tengo el mismo código hash/hash ... Nunca pensé en sincronizar con ellos. Le daré una oportunidad solo para ver. – TofuBeer

+0

eso es inteligente :-) –

+0

Esto no funcionará en el clúster – Vadzim

Cuestiones relacionadas