2012-05-24 6 views
16

das Szenario zwei Methoden Betrachten gibt es in verschiedenen stateless BeanJPA Verschachtelte Transaktionen und Sperren

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 
    } 

} 

Die Transaktion wurde von BeanA.methodA gestartet ausgesetzt werden würde und neue Transaktion würde in BeanB.methodB gestartet werden. Was ist, wenn die MethodeB auf dieselbe Entität zugreifen muss, die von Methode A geändert wurde? Dies würde zu einem Deadlock führen. Ist es möglich, dies zu verhindern, ohne auf Isolationsstufen angewiesen zu sein?

+0

Wie und wo erhalten Sie einen Deadlock? Aus dem Sitzungs-Cache oder aus blockierten Zeilen der Datenbank? –

Antwort

20

Hm, lassen Sie uns alle Fälle auflisten.

REQUIRES_NEW verschachtelt Transaktionen nicht wirklich, aber wie Sie erwähnt pausiert die aktuelle. Es gibt dann einfach zwei Transaktionen, die auf die gleichen Informationen zugreifen. (Dies ist ähnlich wie bei zwei regulären gleichzeitigen Transaktionen, außer dass sie nicht gleichzeitig, sondern im selben Ausführungsthread sind).

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

Dann brauchen wir optimistisch gegen pessimistisch Verriegelung zu prüfen.

Auch müssen wir betrachten spült betrieben von ORMs. Bei ORMs haben wir keine eindeutige Kontrolle, wenn Schreibvorgänge stattfinden, da flush vom Framework gesteuert wird. Normalerweise findet vor dem Commit ein impliziter Flush statt, aber wenn viele Einträge geändert werden, kann das Framework auch Zwischenspülungen durchführen.

1) Betrachten wir optimistisches Sperren, wo Lesen nicht Sperren erhalten, aber schreiben, exklusive Sperren zu erwerben.

Das Lesen von T1 erhält keine Sperre.

1a) Wenn T1 hat die Änderungen bündig prematurly erwarb es eine exklusive Sperre though. Wenn T2 festgeschrieben wird, versucht es, die Sperre zu erlangen, kann dies jedoch nicht. Das System ist blockiert. Dies kann von einer bestimmten Art von Deadlock sein. Die Fertigstellung hängt davon ab, wie Transaktionen oder Sperrzeiten ausfallen.

1b) Wenn T1 die Änderungen nicht bündig hat prematurly hat keine Sperre erworben. Wenn T2 commit, erwirbt und gibt es es frei und ist erfolgreich. Wenn T1 versucht, zu committen, bemerkt es einen Konflikt und schlägt fehl.

2) Lassen Sie uns pessimistische Sperren betrachten, wo lesen gemeinsame Sperren erwerben und exklusive Sperren schreiben.

Die gelesen von T1 erwerben eine gemeinsame Sperre.

2a) Wenn T1 prematurly gespült, es Turnes die Sperre INTA eine exklusive Sperre. Die Situation ist ähnlich wie 1a)

2b) Wenn T1 nicht prematurly spülte, hält T1 eine gemeinsame Sperre. Wenn T2 festschreibt, versucht es, eine exklusive Sperre zu erhalten und blockiert. Das System ist wieder blockiert.

Fazit: es ist mit optimistischem Sperren in Ordnung, wenn keine vorzeitigen Wallungen passieren, die man nicht stricly steuern.

+0

@ewernil Ich habe hier einen Zweifel, jetzt haben wir zwei Transaktionen, die erste Transaktion ist noch nicht abgeschlossen, also wie kann die zweite Transaktion (requires_new) das Ergebnis sehen, das noch nicht von der ersten ausgeführt wurde? Würden Sie bitte etwas Licht auf das Gleiche werfen? –

+0

@SAM Wenn Sie eine Zeile in einer Transaktion ändern, erhalten Sie eine Sperre. Die andere Transaktion kann die alte Zeile lesen, aber die Zeile kann nicht geändert werden, bis die erste Sperre aufgehoben wird. – ewernli

0

durch programmatisches Commit der Transaktion nach entityManager.persist(e1); und vor 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 
    } 

} 

EDIT: CMT

Wenn Sie CMT haben, Sie noch programmatisch begehen kann, erhalten Sie nur die Transaktion von der EJBContext. z.B .: http://geertschuring.wordpress.com/2008/10/07/how-to-use-bean-managed-transactions-with-ejb3-jpa-and-jta/

oder Sie können ein @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void methodC() hinzuzufügen, die die e1.setName("Blah"); entityManager.persist(e1); tun würde, das heißt, es wäre e1 in einer Transaktion bestehen bleiben. dann wäre Ihr methodA()

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

Und was, wenn das nicht möglich ist? Beispiel im Fall von CMT – anergy

+0

seltsamen advise Transaktion in CMT zu begehen, aber dennoch kann es anderes Szenario, in denen dies nicht möglich ist in der Mitte zu begehen, nur weil Sie einige andere Bohne menthod – anergy

+1

fordern, dass nicht das Ziel der EJB ist manuell Transaktion verwalten ... Was ist, wenn nach MethodeB eine Ausnahme auftritt? Kein Rollback möglich ... –

1

nennen Hier ist ein recent article über die Verwendung von REQUIRES_NEW Transaktionsdemarkation.

Aus meiner Erfahrung sollte es keinen Dead-Lock mit Standard-Code geben: Abfragen mit restriktiven where Klausel und wenigen Inserts. In einigen speziellen Fällen kann eine Datenbank-Engine eine Eskalationssperre ausführen, wenn während der Transaktion viele Zeilen in einer einzelnen Tabelle gelesen oder eingefügt werden ... und in diesem Fall kann ja eine Dead-Sperre auftreten.

Aber in diesem Fall kommt das Problem nicht von REQUIRES_NEW, sondern von SQL-Design. Wenn dieses Design nicht verbessert werden kann, haben Sie keine andere Wahl, die Isolationsstufe auf eine lockere Ebene zu ändern.

3

an das Unternehmen Pass und verschmelzen ...

Sie Ihre neue Einheit zu methodB() passieren können, und an die neuen EntityManager verschmelzen. Wenn die Methode zurückgibt Ihre Entität aktualisieren, um die Änderungen zu sehen:

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 
    } 

} 

Beachten Sie, dass dies Ihr Unternehmen begehen wird, wenn die neue Transaktion nach methodB schließt.

... oder sie speichern, bevor methodeB Aufruf

Wenn Sie die Methode über das Unternehmen verwenden wird getrennt von Ihrem Haupttransaktion gespeichert, so dass Sie nicht verlieren alles tun, wenn Sie es von Bean_A retten vor dem Aufruf 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 
    } 

}