0

JDBC でカウンターを実装しようとしています。このメソッドは基本的に列の値をインクリメントし、新しい列の値を返します。以下は私のコードのスニペットです。最初に select .. for update を実行して古い値を取得します (行もロック) 次に、その行で更新を実行します。select .. for update と update の間で、他のトランザクションが列 val の値に干渉できないと仮定するのは正しいことです。

        //get connection here
        con.setAutoCommit(false);
        PreparedStatement statement = con.prepareStatement("SELECT val FROM atable WHERE id=? FOR UPDATE");
        statement.setInt(1, id);
        ResultSet resultSet = statement.executeQuery();
        if (resultSet.next()) {
            oldVal = resultSet.getInt("val");
            statement = con.prepareStatement("UPDATE atable SET val=" + oldVal + "+1 WHERE id=?");
            statement.setInt(1, id);
            int result = statement.executeUpdate();
            if (result != 1) {
                throw new SQLException("error message");
            } else {
                newVal = oldVal + 1;//could do another select statement to get the new value

                statement.close();

            }
        } else {
                throw new SQLException("error message");
        }
        resultSet.close();
        con.commit();
        con.setAutoCommit(true);
       //close connection here

ありがとう。

4

4 に答える 4

0

更新のために選択してから更新するまでの間に、他のトランザクションが列valの値に干渉することはないと想定する権利がありますか?

はい、できます。そうしないと、「データベースの不整合につながる可能性があります。

于 2013-01-10T14:44:07.200 に答える
0

データベースの現在の分離レベルが役割を果たしていることについて何かを追加するつもりでしたが、不確かなため、グーグルで検索したところ、他にも興味深い記事がいくつか見つかりました: SQLServer Questionおよび Oracle-centric answer .

最終的にはわかりません(すみません)。必要に応じて、明示的なアトミック ロックを提供し、コードに新しい値を返すストアド プロシージャを提案するかもしれません。ただし、ストアド プロシージャの構文は、データベース ベンダー固有の場合があります。

(ところで、接続参照を取得する方法は示していません。同時に同じ接続を使用している複数のスレッドがある場合、それらは背後で「自動コミット」属性を変更できます。ほとんどの場合、あなたは接続プーリングを使用しているため、常に 1 つのスレッドだけがこの参照を持っています。)

また、関連性があることが判明した場合、データベースの分離レベルも接続レベルで設定されることに注意してください。そのため、プールから接続を取得している場合、前のユーザーが以前とは異なる状態で接続を残した可能性があります。何が必要。setAutoCommit()属性を復元するのは慎重でした。属性で同じことを行う必要がある場合がありsetTransactionIsolation()ます。

于 2013-01-10T14:46:16.203 に答える
0

Oracle を使用すると、次のことができます。

CallableStatement cs = con.prepareCall(
  "UPDATE atable SET val = val + 1 WHERE id=? RETURNING val INTO ?");
cs.setInt(1, id); 
cs.registerOutParameter(2, Types.NUMERIC); 
cs.execute();
int newId = cs.getInt(2);
于 2013-01-10T14:50:07.110 に答える
0

代わりに、以下のコードのようなことをします。(読みやすくするためにチェックと例外を削除しました。自由に追加してください。)

con.setAutoCommit(false);
PreparedStatement incrementVal = 
    con.prepareStatement("UPDATE atable SET val = val + 1 WHERE id=?");
incrementVal.setInt(1, id);
statement.executeUpdate();
PreparedStatement selectIncrementedVal = 
    con.prepareStatement("SELECT val FROM atable WHERE id=?");
selectIncrementedVal.setInt(1, id);
ResultSet resultSet = selectIncrementedVal.executeQuery();
if (resultSet.next()) {
    newVal = resultSet.getInt("val");
}
resultSet.close();
con.commit();
con.setAutoCommit(true);

以下のトランザクション分離に関する情報を参照してください。

http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html

http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html#isolevel_serializable

于 2013-01-10T14:39:49.017 に答える