【问题标题】:Value from last inserted row in DB [duplicate]数据库中最后插入行的值[重复]
【发布时间】:2010-12-30 21:39:55
【问题描述】:

有没有办法从最后插入的行中获取值?

我正在插入一行,由于创建的序列,PK 将自动增加,我想得到这个序列号。只有 PK 保证在表中是唯一的。

我正在使用带有 JDBC 和 Oracle 的 Java。

我忘了补充说我想使用下面的结果集来检索这个值。 (我已经用 mysql 尝试过,它成功了,但我不得不切换到 Oracle,现在我得到了 ID 的字符串表示,而不是实际的序列号)

Statement stmt = conn.createStatement();
stmt.executeUpdate(insertCmd, Statement.RETURN_GENERATED_KEYS);
stmt.RETURN_GENERATED_KEYS;
ResultSet rs = stmt.getGeneratedKeys();
if(rs.next()){
   log.info("Successful insert");
   id = rs.getString(1);
}

上面的 sn-p 将返回存储在 mysql 表中的列 int 值。但是由于我已经切换到Oracle,返回的值现在是一个奇怪的字符串值。

【问题讨论】:

  • @Nathan 是的,我已经看到了,我刚刚用一个示例更新了我的问题,这与您刚刚发布的链接相似。
  • 这个问题不是链接问题的重复,因为这个问题是针对 Oracle 的,而另一个问题是针对 PostgreSQL 的。由于 Oracle 和 PostgreSQL 之间的功能差异,其他问题的最佳答案不能用作此问题的答案。
  • getGeneratedKeys() 中返回的唯一列是 ROWID - oracle 11。我认为这是您提到的“奇怪”值。

标签: java database oracle jdbc


【解决方案1】:

您要做的是利用RETURNING 子句。让我们设置一个示例表和序列:

CREATE TABLE "TEST" 
( "ID" NUMBER NOT NULL ENABLE, 
 "NAME" VARCHAR2(100 CHAR) NOT NULL ENABLE, 
  CONSTRAINT "PK_TEST" PRIMARY KEY ("ID")
  );

CREATE SEQUENCE SEQ_TEST;

现在,您的 Java 代码应该如下所示:

String insertSql = "BEGIN INSERT INTO TEST (ID, NAME) VALUES (SEQ_TEST.NEXTVAL(), ?) RETURNING ID INTO ?; END;";
java.sql.CallableStatement stmt = conn.prepareCall(insertSql);
stmt.setString(1, "John Smith");
stmt.registerOutParameter(2, java.sql.Types.VARCHAR);
stmt.execute();
int id = stmt.getInt(2);

【讨论】:

  • 我真的认为为此使用触发器是一个可怕的解决方案:它使代码的可移植性更差,并且毫无意义地增加了额外的复杂性。
  • 原始问题指出“我正在插入一行,由于创建的序列,PK 将自动增加......”如果不使用触发器填充主数据库,您将如何在 Oracle 中执行此操作钥匙?
  • @Pascal 触发器是绝对可移植的。
  • @Adam 使用绝对不需要必要的触发器,您可以在 INSERT 语句中包含seq.nextval。不,真的,我觉得这种方法不必要的麻烦,我不明白为什么人们喜欢它。
  • 我没有说“我”这样做,但我根据这个问题做了一个假设。是的,我想这确实使我的答案比必要的复杂一些。我可能会更新 SQL,但我的答案的关键是使用 RETURNING 子句来获取 PK 的值。
【解决方案2】:

这与其他数据库不一致,但在使用 Oracle 时,getGeneratedKeys() 在使用 Statement.RETURN_GENERATEDKEYS 时返回插入行的 ROWID。所以你需要使用oracle.sql.ROWID 专有类型来“读取”它:

Statement stmt = connection.createStatement();
stmt.executeUpdate(insertCmd, Statement.RETURN_GENERATED_KEYS);
ResultSet rs = stmt.getGeneratedKeys();
oracle.sql.ROWID rid = (oracle.sql.ROWID) rs.getObject(1); 

但这不会为您提供生成的 PK ID。使用 Oracle 时,您应该使用方法executeUpdate(String sql, int[] columnIndexes)executeUpdate(String sql, String[] columnNames) 而不是executeUpdate(String sql, int autoGeneratedKeys) 来获取生成的序列值。像这样(调整值以匹配索引或主键列的名称):

stmt.executeUpdate(INSERT_SQL, new int[] {1});
ResultSet rs = stmt.getGeneratedKeys();

或者

stmt.executeUpdate(INSERT_SQL, new String[] {"ID"});
ResultSet rs = stmt.getGeneratedKeys();

虽然对此进行了更多研究,但似乎这种方法显示在 Spring documentation(如 here 中提到的)中,所以,我想这不可能是完全错误的。但是,不幸的是,它并不是真正可移植的,并且可能无法在其他平台上运行。

【讨论】:

  • 没有成功,我遇到了语法错误。我会看看我做错了什么并给出更新。
  • 我不明白 Pascal 是您反对 RETURNING 子句,但提出了一个需要两次数据库调用的解决方案。 Adam 的解决方案的好处是它是一个单一的数据库调用。
  • @David 我不太喜欢触发器,主要是因为它们在幕后做一些“神奇”的事情,隐秘的,而且因为我不认为在 Java 和数据库上传播逻辑是一种好主意。因此,如果可以避免它们,我不会使用它们。关于“两个数据库调用”,你能不能详细说一下,我不确定我是否在关注你。
  • 这太棒了,我评论这个只是为了记住这个黑客:)
【解决方案3】:

您应该改用ResultSet#getLong()。如果无效,请尝试 ResultSet#getRowId() 并最终将其转换为 oracle.sql.ROWID。如果返回的十六进制字符串实际上是十六进制的ID,那么您可以尝试通过Long#valueOf()Integer#valueOf()将其转换为十进制。

Long id = Long.valueOf(hexId, 16);

也就是说,Oracle 的 JDBC 驱动已经很久不支持ResultSet#getGeneratedKeys() 了,还是有点麻烦。如果您无法做到这一点,那么您需要在与执行insert 相同的语句上执行SELECT CURRVAL(sequencename),或者如果它是PreparedStatement,则在同一事务中执行新语句。基本示例:

public void create(User user) throws SQLException {
    Connection connection = null;
    PreparedStatement preparedStatement = null;
    Statement statement = null;
    ResultSet generatedKeys = null;

    try {
        connection = daoFactory.getConnection();
        preparedStatement = connection.prepareStatement(SQL_INSERT);
        preparedStatement.setValue(1, user.getName());
        // Set more values here.
        int affectedRows = preparedStatement.executeUpdate();
        if (affectedRows == 0) {
            throw new SQLException("Creating user failed, no rows affected.");
        }
        statement = connection.createStatement();
        generatedKeys = statement.executeQuery(SQL_CURRVAL);
        if (generatedKeys.next()) {
            user.setId(generatedKeys.getLong(1));
        } else {
            throw new SQLException("Creating user failed, no generated key obtained.");
        }
    } finally {
        close(generatedKeys);
        close(statement);
        close(preparedStatement);
        close(connection);
    }
}

哦,从您的代码示例中,以下行

stmt.RETURN_GENERATED_KEYS;

完全是多余的。去掉它。

您可以找到here 另一个我之前发布的关于获取生成密钥的示例,它使用普通的getGeneratedKeys() 方法。

【讨论】:

  • 我尝试了 ResultSet#getLong(),它不起作用。返回的错误是“无效的列类型:类 oracle.jdbc.driver.T4CRowidAccessor 未实现 getLong”我将尝试您的第二个建议。
  • 试试java.sql.RowIdoracle.sql.ROWID
  • 以下:oracle.sql.ROWID rid = rs.getObject(1); ID = rid.stringValue(); ID 包含一个十六进制值,这正是我之前使用 rs.getString(1); 得到的。将尝试您的最后建议。
  • 也许它只是十六进制格式的ID。尝试将其转换为十进制。
  • 您的建议应该有效,但也许是因为我从 oracle.sql.ROWID 返回的值不是十六进制值?我收到此错误: java.lang.NumberFormatException: For input string: "AAAS+XAAGAAAAEvAAP" ~~~~ 引号中的值是我从 ROWID.stringValue() 得到的值;
猜你喜欢
  • 2012-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-28
相关资源
最近更新 更多