JDBC select 并在一个请求中更新

JDBC select and update in one request

这是 oracle 数据库中的 table:

ID varchar2() primary key, 
STATUS varchar2(),
....

我需要 select 状态为 'DELAYED' 的行并同时设置状态 'PROGRESS'。

有什么方法可以使用 JDBC 吗?

如果可以创建辅助包

您可以使用 PL/SQL UPDATE .. RETURNING 语法。我认为 BULK COLLECT INTO 语义仍然不能直接与 jdbc 一起使用,所以你必须使用匿名 PL/SQL 块和一些技巧,而不是:

-- An auxiliary package is needed to declare the type.
create or replace package p as 
  type t is table of my_table%rowtype;
end p;
/

declare
  r p.t;
  c sys_refcursor;
begin
  update my_table
  set status = 'PROGRESS'
  where status = 'DELAYED'
  returning id, status, ...
  bulk collect into r;

  -- The package is needed for this syntax, which cannot yet work on types
  -- declared in anonymous blocks:
  open c for select * from table(r);
  dbms_sql.return_result(c);
end;
/

然后获取结果。不幸的是,从这个博客 post 中可以看出这有点痛苦: https://blog.jooq.org/2017/07/13/how-i-incorrectly-fetched-jdbc-resultsets-again/

(特别是,根据 ojdbc 版本,获取 DBMS_SQL.RETURN_RESULT 结果时存在错误)

// Alternatively, use a prepared statement if needed
try (Statement s = con.createStatement()) {

    fetchLoop:
    for (int i = 0, updateCount = 0; i < 256; i++) {
        boolean result = (i == 0)
            ? s.execute(
                """
                declare
                  r p.t;
                  c sys_refcursor;
                begin
                  ...
                end;
                """)
            : s.getMoreResults();

        if (result)
            try (ResultSet rs = s.getResultSet()) {
                // Consume result set
            }
        else if ((updateCount = s.getUpdateCount()) != -1)
            System.out.println("Update Count: " + updateCount);
        else
            break fetchLoop;
    }
}

您还可以获取 SYS_REFCURSOR 值作为 CallableStatement

OUT 参数

如果您无法创建包

如果您不能创建包,那么您可以使用 DBMS_SQL.NUMBER_TABLE 等序列化各个列,但这更复杂。它看起来大致像这样:

declare
  o0 dbms_sql.number_table;
  o1 dbms_sql.varchar2_table;
  -- repeat for all columns
  c0 sys_refcursor;
  c1 sys_refcursor;
  -- repeat for all columns
begin
  update my_table
  set status = 'PROGRESS'
  where status = 'DELAYED'
  returning id, status, ...
  bulk collect into o0, o1;
  -- Optional rowcount if you need that, as an OUT parameter
  ? := sql%rowcount;
  open c0 for select * from table(o0);
  open c1 for select * from table(o1);
  -- These can also be fetched as OUT parameters if you want
  ? := c0;
  ? := c1;
end;

这可能会导致更多的网络流量,并且需要将多个每列结果集重新组装成一个结果集,所以如果可以的话,我推荐包方法。

这就是当您 运行 一个 UPDATE .. RETURNING 语句时 jOOQ 在幕后所做的事情。

由于您不能直接在 JDBC 中这样做,您可以使用以下代码片段分两步完成,希望对您有所帮助:

这里应该使用jdbc:oracle作为驱动,update ... set ... where语法

首先你必须调用 updateTable() 将所有 DELAYED 行更改为 PROGRESS然后调用 getAllDelayedObjects() 获取所有 DELAYED 行作为 List.

(使用您自己的 实体类型,您希望保留数据库结果(在 getAllDelayedObjects() 中)而不是 YourEntityClass OR 你可以在控制台中打印 resultSet)

public class DataAccess {
    private Connection connection;
    private Statement statement;

    public boolean updateTable() throws SQLException {
        openConnection();
        try (PreparedStatement statement=
                connection.prepareStatement("update yourTableName set STATUS=? where STATUS=?")
        ) {
            statement.setString(1, "DELAYED");
            statement.setString(2, "PROGRESS");
            
            if (statement.executeUpdate()>0) {
                closeConnection();
                return true;
            }
            else {
                closeConnection();
                return false;
            }
        }

        catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    public List<YourEntityClass> getAllDelayedObjects() throws SQLException {
        openConnection();
        try (
                PreparedStatement statement = connection.prepareStatement("select * from users where STATUS=?")
        ) {
            
            statement.setString(2, "PROGRESS");
            ResultSet resultSet = statement.executeQuery();

            List<YourEntityClass> entityObjects = new ArrayList<>();

            while (resultSet.next()) {
                YourDaType object = new YourDaType();
                entityObject.setId(resultSet.getInt("ID"));
                entityObject.setStatus(resultSet.getString("STATUS"));
                // and whatever more properties you have in the table

                entityObjects.add(object);
            }
            closeConnection();
            return entityObjects;
        }
        catch (Exception e) {
            closeConnection();
            e.printStackTrace();
            return null;
        }
    }

    public void openConnection() throws SQLException {
        String username = "yourUserNameForDB";
        String pass = "yourPasswordForDB";
        connection = DriverManager.getConnection("jdbc:oracle://yourDBdomain:yourDBport/yourDataBaseName", username, pass);
    }

    public void closeConnection() throws SQLException {
        this.connection.close();
    }
}