0

我正在尝试在 JDBC 中实现一个计数器,该方法本质上是增加一个列值并返回新的列值,下面是我的代码片段我首先执行 select .. for update 以获取旧值(也导致一行lock) 然后对该行执行更新。我是否可以假设没有其他事务可以干扰 select .. for 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

我会做类似下面的代码。(为了可读性,我取出了检查和异常;随时将它们添加回来。)

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 回答
0

我是否可以假设没有其他事务可以干扰 select .. for update 然后 update 之间的列 val 的值

是的,您可以。否则可能会导致“数据库不一致”。

于 2013-01-10T14:44:07.200 回答
0

我打算添加一些关于数据库当前隔离级别的作用,但在我不确定的情况下,我四处搜索,发现了其他一些有趣的文章: SQLServer Question and Oracle-centric answer

最终,我不确定(对不起)。我可能会建议一个存储过程来提供您想要的显式原子锁定,并让它在需要时将新值返回给您的代码。但是,存储过程语法可能是特定于数据库供应商的。

(顺便说一句,您没有展示如何获取 Connection 引用。如果有多个线程同时使用同一个连接,他们将能够在您背后修改“自动提交”属性。很可能您是使用连接池,因此在任何给定时间只有一个线程具有此引用。)

另请注意,数据库隔离级别(如果结果是相关的)也设置在连接级别上,因此如果您从池中获取连接,则前一个用户可能已将连接置于与之前不同的状态你需要什么。您谨慎地恢复了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 回答