2012-05-24 18 views
16

en cuenta la situación dos métodos existe en diferentes bean sinTransacciones anidadas APP y el bloqueo

public class Bean_A { 
    Bean_B beanB; // Injected or whatever 
    public void methodA() { 
    Entity e1 = // get from db 
    e1.setName("Blah"); 
    entityManager.persist(e1); 
    int age = beanB.methodB(); 

    } 
} 
public class Bean_B { 
    //Note transaction 
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) 
    public void methodB() { 

    // complex calc to calculate age 
    } 

} 

La transacción iniciada por BeanA.methodA sería suspendido y nueva transacción se habría iniciado en BeanB.methodB. ¿Qué pasa si el método B necesita acceder a la misma entidad que fue modificada por el método A? Esto daría lugar a un punto muerto. ¿Es posible evitarlo sin depender de niveles de aislamiento?

+0

¿Cómo y dónde se produce un punto muerto? ¿Desde el caché de la sesión o desde las filas bloqueadas de la base de datos? –

Respuesta

20

Hm, vamos a enumerar todos los casos.

REQUIRES_NEW no anida realmente las transacciones, pero como mencionas hace una pausa en la actual. Entonces, simplemente hay dos transacciones que acceden a la misma información. (Esto es similar a dos transacciones concurrentes regulares, excepto que no son concurrentes, sino en el mismo hilo de ejecución).

T1 T2   T1 T2 
―    ― 
|    | 
       | 
    ―   | ― 
    |   | | 
    |  =  | | 
    ―   | ― 
       | 
|    | 
―    ― 

entonces tenemos que considerar optimista vs pesimista bloqueo.

Además, tenemos que considerar vaciados operados por ORMs. Con los ORM, no tenemos un control claro cuando se producen escrituras, ya que flush está controlado por el marco. Por lo general, un color implícito ocurre antes de la confirmación, pero si se modifican muchas entradas, el marco también puede hacer descargas intermedias.

1) Consideremos el bloqueo optimista, donde leer no adquiere bloqueos, pero escribir adquiere bloqueos exclusivos.

La lectura por T1 no adquiere un bloqueo.

1a) Si T1 sí enjuagó los cambios prematuramente, adquirió un bloqueo exclusivo. Cuando T2 se compromete, intenta adquirir el bloqueo pero no puede. El sistema está bloqueado. Esto puede ser un tipo de punto muerto en particular. La finalización depende de cómo transcurran las transacciones o bloqueos.

1b) Si T1 no eliminó los cambios prematuramente, no se ha adquirido ningún bloqueo. Cuando T2 se compromete, lo adquiere y lo libera y es exitoso. Cuando T1 intenta comprometerse, nota un conflicto y falla.

2) Consideremos el bloqueo pesimista, donde leer adquiere bloqueos compartidos y escribe bloqueos exclusivos.

La lectura por T1 adquiere un bloqueo compartido.

2a) Si T1 se enjuaga prematuramente, se convierte en una cerradura exclusiva. La situación es similar a 1a)

2b) Si T1 no se descargó prematuramente, T1 tiene un bloqueo compartido. Cuando T2 se compromete, intenta adquirir un bloqueo y bloques exclusivos. El sistema está bloqueado de nuevo.

Conclusión: está bien con un bloqueo optimista si no ocurre un lavado prematuro, que no se puede controlar estrictamente.

+0

@ewernil Tengo una duda aquí, ahora tenemos dos transacciones, la primera transacción aún no se ha completado, entonces ¿cómo puede la segunda transacción (requires_new) ver el resultado que aún no ha sido comprometido por el primero? ¿Podrías por favor arrojar algo de luz sobre lo mismo? –

+0

@SAM cuando modifica una fila en una transacción, adquiere un bloqueo. La otra transacción puede leer la fila anterior pero no puede modificar la fila hasta que se libere el primer bloqueo. – ewernli

0

al comprometerse programáticamente la transacción después de entityManager.persist(e1); y antes de int age = beanB.methodB();?

public class Bean_A { 
    Bean_B beanB; // Injected or whatever 
    public void methodA() { 
    EntityManager em = createEntityManager(); 
    Entity e1 = // get from db 
    e1.setName("Blah"); 
    entityManager.persist(e1); 
    em.getTransaction().commit(); 
    int age = beanB.methodB(); 

    } 
} 
public class Bean_B { 
    //Note transaction 
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) 
    public void methodB() { 

    // complex calc to calculate age 
    } 

} 

EDITAR: CMT

Si tiene CMT, todavía se puede cometer mediante programación, que acaba de obtener la operación desde el EJBContext. e.g .: http://geertschuring.wordpress.com/2008/10/07/how-to-use-bean-managed-transactions-with-ejb3-jpa-and-jta/

o puede agregar un @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void methodC() que haría el e1.setName("Blah"); entityManager.persist(e1);, es decir, persistiría e1 en una transacción. entonces su methodA() llamarían

methodC(); 
beanB.methodB(); 
+0

¿Y si eso no es posible? Ejemplo en el caso de CMT – anergy

+0

consejos extraños para confirmar la transacción en CMT pero, sin embargo, puede haber otro escenario en el que no sea factible confirmar en el medio simplemente porque está llamando a algún otro mento de bean – anergy

+1

Ese no es el objetivo de EJB hacerlo manualmente gestionar la transacción ... ¿Qué ocurre si se produce una excepción después del método B? No hay reversión posible ... –

1

Aquí es una recent article sobre el uso de REQUIRES_NEW demarcación de transacciones.

Desde mi experiencia, no debería haber deadlock con código estándar: consultas con cláusula restrictiva where y pocas inserciones. En algunos casos específicos, algunos motores de base de datos pueden bloquear el escalamiento si hay muchas filas leídas o insertadas en una sola tabla durante la transacción ... y en ese caso, sí puede producirse un bloqueo.

Pero en ese caso, el problema no viene del REQUIRES_NEW sino del diseño de SQL. Si ese diseño no se puede mejorar, entonces no tiene otra opción para cambiar el nivel de aislamiento a un nivel más suelto.

3

Pasar la entidad y se funden ...

Puede pasar su nueva entidad para methodB() y fusionarlo con el nuevo EntityManager. Cuando el procedimiento vuelve a refrescar la entidad para ver los cambios:

public class Bean_A { 
    Bean_B beanB; // Injected or whatever 
    public void methodA() { 
    Entity e1 = // get from db 
    e1.setName("Blah"); 
    entityManager.persist(e1); 
    int age = beanB.methodB(e1); 
    entityManager.refresh(e1); 
    } 
} 

public class Bean_B { 
    //Note transaction 
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) 
    public void methodB(Entity e1) { 
    e1 = entityManager.merge(e1); 
    // complex calc to calculate age 
    } 

} 

Tenga en cuenta que esto va a comprometer su entidad cuando la nueva transacción se cierra después de methodB.

... o guardarlo antes de llamar methodB

Si se utiliza el método anterior la entidad se guarda aparte de su operación principal, por lo que no suelta nada si lo guarda de Bean_A antes de llamar methodB():

public class Bean_A { 
    Bean_B beanB; // Injected or whatever 

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) 
    public void createEntity() { 
    Entity e1 = // get from db 
    e1.setName("Blah"); 
    entityManager.persist(e1); 
    } 

    public void methodA() { 
    createEntity() 
    int age = beanB.methodB(); 
    } 
} 

public class Bean_B { 
    //Note transaction 
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) 
    public void methodB() { 

    // complex calc to calculate age 
    } 

} 
Cuestiones relacionadas