2012-04-04 8 views
2

Ich habe eine Anforderung in meiner Java-Anwendung, eine Reihe von DB-Anweisungen in einer atomaren & isoliert auszuführen. Beispielsweise muss die Anwendung Datenzeilen aus einer Tabelle lesen und eine Datenzeile in einer anderen Tabelle aktualisieren.Eine Reihe von DB-Operationen in Java sperren

QueryRunner queryRunner = new QueryRunner(); // DBUtils query runner 

Object[] params = new Object[] { param }; 

Connection conn = null; 
try { 
    conn = ...; // get connection 
    conn.setAutoCommit(false); 
    result = queryRunner.query(conn, "select x, y, z from table1 where column1 = ?", new BeanHandler<SomeBean>(SomeBean.class), params); 

    // logic to get value for update 

    queryRunner.update(conn, "update table2 set p = ? where q = ?", some_value, some_id); 
    conn.commit(); 
} catch (SQLException e) { 
    // 
} finally { 
    DBUtils.closeQuietly(conn); 
} 

Das Transaktionsmanagement durch automatische Einstellung erreicht wird verpflichten für die Verbindung zu falschen und ausdrücklich später begehen, wie oben gezeigt. Aber der obige Code kann auch in einer Multithread-Umgebung ausgeführt werden, und ich möchte auch, dass die beiden DB-Anweisungen (Select & update) ausschließlich als Ganzes ausgeführt werden.

Ich habe eine Idee, ein gemeinsam genutztes Java Lock-Objekt in dieser Methode zu verwenden, unten abgebildet.

in der Klasse

private Lock lock = new ReentrantLock(); // member variable 

Bei dem Verfahren

lock.lock(); 
try { 
    conn = ...; // get connection 
    conn.setAutoCommit(false); 

    result = queryRunner.query(conn, "select x, y, z from table1 where column1 = ?", new BeanHandler<SomeBean>(SomeBean.class), params); 

    // logic to get value for update 

    queryRunner.update(conn, "update table2 set p = ? where q = ?", some_value, some_id); 
    conn.commit(); 
} finally { 
    DBUtils.closeQuietly(conn); 
    lock.unlock(); 
} 

Es scheint eine Art fähig, das Problem zu lösen. Ich frage mich jedoch, ob dies die beste Vorgehensweise ist und gibt es dafür eine bessere Alternative (z. B. Rahmen)?

Antwort

1

Mein Vorschlag ist, dass die Datenbank diese Sperren für Sie anstelle Ihrer Anwendung verwaltet. Dies behandelt den Fall, in dem mehrere JVMs den Code ausführen. Die von Ihnen erwähnten Verriegelungsmechanismen können nur in einer einzelnen JVM wirksam sein.

Der Weg, dies zu erreichen, ist eine SELECT ... FOR UPDATE zu tun. Dadurch werden die ausgewählten Zeilen gesperrt und die Sperre wird aufgehoben, wenn Ihre Transaktion festgeschrieben oder zurückgesetzt wird. Dies ist besser als eine Sperre auf Tabellenebene, da diese Zeilen weiterhin von anderen Transaktionen gelesen werden können, die nur den aktuellen Wert lesen, aber nicht aktualisieren möchten. Wenn eine andere Transaktion versucht, eine FOR UPDATE Sperre zu erhalten, wird sie blockiert, bis die erste abgeschlossen ist.

+0

Danke, Hiro2k. Unsere Anwendung läuft in einem Servlet-Container und wird in einer einzigen JVM sein, glaube ich. Das "SELECT ... FOR UPDATE" funktioniert möglicherweise nicht, wenn ich eine Zeile in einer anderen Tabelle als der Tabelle aktualisiere, in der die SELECT-Anweisung ausgeführt wird. – Chen

+0

Richtig, aber wenn Sie sich jemals entscheiden, eine weitere Instanz des Servers zum Lastenausgleich oder zur Skalierung hinzuzufügen, stoßen Sie auf dieses Problem. Sie können die SELECT-Anweisung für UPDATE in der Zeile in der zweiten Tabelle ganz zu Beginn Ihrer Methode hinzufügen, damit andere warten müssen, bis sie beendet ist. – Hiro2k

+0

Guter Punkt, @ Hiro2k. Ich werde unsere Anwendungsfälle an meinem Ende bewerten, um zu sehen, ob alle von ihnen abgedeckt werden können. – Chen

1

Die einzige Möglichkeit, die Atomicity zu erreichen, die Sie benötigen, ist die Verwendung einer gespeicherten Prozedur in der Datenbank, um die Daten zu isolieren und sie alle gleichzeitig zu sperren. Das Sperren auf Java-Ebene kann nicht so einfach sein wie das Sperren in der Datenbank.

+0

Mein Vorschlag funktioniert nur, wenn die relevanten Daten durch die Methodenaufrufe für das gleiche Objekt in einer einzigen JVM aktualisiert werden. Es wird die Datenbankebene per se nicht gesperrt. Dies verhindert jedoch nicht, dass andere unerwartete Aktualisierungen desselben Datenelements an anderer Stelle ausgelöst werden (z. B. bei anderen Methodenaufrufen oder anderen Anwendungen). Kann dies durch die Verwendung gespeicherter Prozeduren gemildert werden? – Chen

1

Eine andere Möglichkeit, wie Sie solche Probleme behandeln können, besteht darin, die serialisierbare Transaktionsisolationsstufe für alle Ihre Datenbanktransaktionen zu verwenden. Dies bewirkt, dass sich jede Gruppe von Transaktionen so verhält, als ob sie einzeln ausgeführt würden, ohne dass sie einzeln ausgeführt werden. Wenn Sie dies tun, sollten Sie ein Framework verwenden, das Serialisierungsfehler (SQLState 40001) abfängt und die Transaktionen erneut versucht. Die große Sache ist, dass Sie sich keine Gedanken über bestimmte Interaktionen zwischen Transaktionen machen müssen - wenn eine Transaktion das Richtige tut, wenn sie das einzige ist, das läuft, wird sie in jedem Transaktionsmix das Richtige tun.

Beachten Sie, dass alle Transaktionen serialisierbar sein müssen, damit dies so einfach funktioniert.

0

Meinen Sie, dass Sie diesen Code-Block, der Select- und Update-Anweisungen enthält, nur als Thread-sicher definieren möchten? Dafür wird das synchronisierte Keyword verwendet. Auch wenn diese Frage lange zurück gestellt wird, möchte ich sie hier nur notieren. Legen Sie diese Codezeilen unter synchronisierten Block.