JDBC PreparedStatement始终返回1作为自动生成的密钥

时间:2013-02-13 01:13:27

标签: java mysql jdbc

我有这段代码试图在数据库中插入一条记录:

try {
 Connection conn = getConnection();

 String sql = 
   "INSERT INTO myTable(userId,content,timestamp) VALUES(?,?,NOW())";
 PreparedStatement st = 
    conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);

 st.setLong(1, userId);
 st.setString(2, content);
 id = st.executeUpdate(); //this is the problem line            
} catch(Exception e) {}

问题是,虽然正确插入了记录,但我希望id包含刚刚插入的记录的主键+ auto_increment id。但是,出于某种原因,它总是返回'1'作为id,可能是因为插入过程中userId的值为1。

我的桌子是InnoDB。起初userId是另一个表的外键,不管我已经删除了外键,甚至是userId列上的索引,但我仍然得到1作为返回值。

任何想法我做错了什么?

5 个答案:

答案 0 :(得分:8)

  

<强> PreparedStatment.executeUpdate()

     

<强>返回:
  (1)SQL数据操作语言(DML)语句的行计数或(2)0表示不返回任何内容的SQL语句

您需要使用execute()代替ResultSet获取getGeneratedKeys();它将包含您想要的数据。

编辑添加:我读了你的问题,因为表中有一个 userId

的自动增量字段

答案 1 :(得分:6)

Brian Roach的accepted Answer是正确的。我正在添加一些想法和完整代码的示例。

RETURN_GENERATED_KEYS 是否意味着“返回生成的密钥”

原始海报似乎被标志Statement.RETURN_GENERATED_KEYS的措辞混淆,可以理解。与直觉相反,传递此标志不会 更改PreparedStatement::executeUpdate方法的行为。该方法始终返回int,即受执行的SQL影响的行数。 “executeUpdate”方法永远不会返回生成的键。

int countRowsAffected = pstmt.executeUpdate();  // Always return number of rows affected, *not* the generated keys.

问,并且你会收到

如果您想要生成密钥,则必须执行两个步骤:

  1. 传递旗帜
  2. 要求ResultSet由仅包含生成的键值的行组成。
  3. 这种安排允许您添加获取生成的键的行为,同时保持其他所需的行为,计算受影响的行数。

    示例代码

    这是一个几乎真实的例子,它来自Java 8应用程序,它从数据源中删除数据。我认为在这种情况下,一个完整的例子可能比最小的例子更有用。

    次要细节......这段代码可能不完美,语法或其他方面,因为我复制粘贴修改的真实源代码。我使用UUID data type而不是整数作为我的表的surrogate主键。类CharHelperDBHelper是我自己的,其详细信息在这里并不重要。 xy变量是我自己应用的有意义数据的替代品。我的日志记录调用是SLF4J框架。 UUID hex字符串是将日志中的报表链接回原始源代码的便捷方式。数据库是Postgres,但是这种代码应该适用于支持生成密钥报告的任何数据库。

    public UUID dbWrite (  String x , String y , DateTime whenRetrievedArg ) {
        if ( whenRetrievedArg == null ) {
            logger.error( "Passed null for whenRetrievedArg. Message # 2112ed1a-4612-4d5d-8cc5-bf27087a350d." );
            return null;
        }
    
        Boolean rowInsertComplete = Boolean.FALSE; // Might be used for debugging or logging or some logic in other copy-pasted methods.
    
        String method = "Method 'dbWrite'";
        String message = "Insert row for some_table_ in " + method + ". Message # edbea872-d3ed-489c-94e8-106a8e3b58f7.";
        this.logger.trace( message );
    
        String tableName = "some_table_";
    
        java.sql.Timestamp tsWhenRetrieved = new java.sql.Timestamp( whenRetrievedArg.getMillis() );  // Convert Joda-Time DatTime object to a java.sql.Timestamp object.
    
        UUID uuidNew = null;
    
        StringBuilder sql = new StringBuilder( AbstractPersister.INITIAL_CAPACITY_OF_SQL_STRING ); // private final static Integer INITIAL_CAPACITY_OF_SQL_STRING = 1024;
        sql.append( "INSERT INTO " ).append( tableName ).append( CharHelper.CHAR.PAREN_OPEN_SPACED ).append( " x_ , y_ " ).append( CharHelper.CHAR.PAREN_CLOSED ).append( DBHelper.SQL_NEWLINE );
        sql.append( "VALUES ( ? , ? , ?  ) " ).append( DBHelper.SQL_NEWLINE );
        sql.append( ";" );
    
        try ( Connection conn = DBHelper.instance().dataSource().getConnection() ;
    

    我们执行第1步,传递RETURN_GENERATED_KEYS标记。

                PreparedStatement pstmt = conn.prepareStatement( sql.toString() , Statement.RETURN_GENERATED_KEYS ); ) {
    

    我们继续准备并执行该声明。请注意,int countRows = pstmt.executeUpdate();返回受影响行的计数,而不是生成的键。

            pstmt.setString( 1 , x ); 
            pstmt.setString( 2 , y ); 
            pstmt.setTimestamp( 3 , tsWhenRetrieved );  
            // Execute
            int countRows = pstmt.executeUpdate();  // Always returns an int, a count of affected rows. Does *not* return the generated keys.
            if ( countRows == 0 ) {  // Bad.
                this.logger.error( "Insert into database for new " + tableName + " failed to affect any rows. Message # 67e8de7e-67a5-42a6-a4fc-06929211e6e3." );
            } else if ( countRows == 1 ) {  // Good.
                rowInsertComplete = Boolean.TRUE;
            } else if ( countRows > 1 ) {  // Bad.
                rowInsertComplete = Boolean.TRUE;
                this.logger.error( "Insert into database for new " + tableName + " failed, affecting more than one row. Should not be possible. Message # a366e215-6cf2-4e5c-8443-0b5d537cbd68." );
            } else { // Impossible.
                this.logger.error( "Should never reach this Case-Else with countRows value " + countRows + " Message # 48af80d4-6f50-4c52-8ea8-98856873f3bb." );
            }
    

    这里我们执行步骤#2 ,请求生成密钥的ResultSet。在这个例子中,我们插入了一行并期望返回一个生成的密钥。

            if ( rowInsertComplete ) {
                // Return new row’s primary key value.
                ResultSet genKeys = pstmt.getGeneratedKeys();
                if ( genKeys.next() ) {
                    uuidNew = ( UUID ) genKeys.getObject( 1 );  // ResultSet should have exactly one column, the primary key of INSERT table.
                } else {
                    logger.error( "Failed to get a generated key returned from database INSERT. Message # 6426843e-30b6-4237-b110-ec93faf7537d." );
                }
            }
    

    其余的是错误处理和清理。请注意我们返回UUID ,即生成的插入记录的主键,位于此代码的底部。

        } catch ( SQLException ex ) {
            // We expect to have occasional violations of unique constraint on this table in this data-scraping app.
            String sqlState = ex.getSQLState();
            if ( sqlState.equals( DBHelper.SQL_STATE.POSTGRES.UNIQUE_CONSTRAINT_VIOLATION ) ) {  // SqlState code '23505' = 'unique_violation'.
                this.logger.trace( "Found existing row when inserting a '" + tableName + "' row for y: " + y + ". Expected to happen on most attempts. Message # 0131e8aa-0bf6-4d19-b1b3-2ed9d333df27." );
                return null; // Bail out.
            } else { // Else any other exception, throw it.
                this.logger.error( "SQLException during: " + method + " for table: " + tableName + ", for y: " + y + ". Message # 67908d00-2a5f-4e4e-815c-5e5a480d614b.\n" + ex );
                return null; // Bail out.
            }
        } catch ( Exception ex ) {
            this.logger.error( "Exception during: " + method + " for table: " + tableName + ", for y: " + y + ". Message # eecc25d8-de38-458a-bb46-bd6f33117969.\n" + ex );
            return null;  // Bail out.
        }
    
        if ( uuidNew == null ) {
            logger.error( "Returning a null uuidNew var. SQL: {} \nMessage # 92e2374b-8095-4557-a4ed-291652c210ae." , sql );
        }
        return uuidNew;
    }
    

答案 2 :(得分:2)

String SQLQuery=" ";

String generatedKeys[]= {"column_name"};//'column_name' auto-increment column

prepSt = Connection.prepareStatement(SQLQuery,generatedKeys);

prepSt.setInt(1, 1234); 

.....

.....

....


prepSt.executeUpdate();

ResultSet rs = prepSt.getGeneratedKeys; // used same PreparedStatement object as used   for Insert .


if(rs.next()) {


int id=rs.getLong("column_name");


System.out.println(id);


}
} catch (SQLException e) {
}

答案 3 :(得分:1)

如果您在数据库中设置了userId自动增量,则不应尝试自行添加。您应该插入NULL,它会自动为您增加! (线索在名称中!)

此外,您没有更新您的表,您正在插入它。所以你不要执行更新()。尝试...

PreparedStatement pst = conn.prepareStatement("INSERT INTO myTable(userId,content,timestamp) VALUES(NULL,?,NOW())");
pst.setString(1, content);
pst.executeQuery();

答案 4 :(得分:1)

你得到的是'Rows insetrted'的通知(对于INSERT语句)。我们使用此方法来了解我们的DML查询是否成功。以下是使用[prepareStatement(yourSQL,Statement.RETURN_GENERATED_KEYS)]获取自动生成ID的方法。请注意,此方法仅返回RowID引用。要获得实际的val,请参阅方法2。

(方法1)

Try{
String yourSQL="insert into Table1(Id,Col2,Col3) values(SEQ.nextval,?,?)";
myPrepStatement = <Connection>.prepareStatement(yourSQL, Statement.RETURN_GENERATED_KEYS);
myPrepStatement.setInt(1, 123); 
myPrepStatement.setInt(2, 123); 

myPrepStatement.executeUpdate();
ResultSet rs = getGeneratedKeys;
if(rs.next()) {
  java.sql.RowId rid=rs.getRowId(1); 
  //what you get is only a RowId ref, try make use of it anyway U could think of
  System.out.println(rid);
}
} catch (SQLException e) {
}

(方法2)

Try{
String yourSQL="insert into Table1(Id,Col2,Col3) values(SEQ.nextval,?,?)";
//IMPORTANT: here's where other threads don tell U, you need to list ALL cols 
//mentioned in your query in the array
myPrepStatement = <Connection>.prepareStatement(yourSQL, new String[]{"Id","Col2","Col3"});
myPrepStatement.setInt(1, 123); 
myPrepStatement.setInt(2, 123); 
myPrepStatement.executeUpdate();
ResultSet rs = getGeneratedKeys;
if(rs.next()) {
//In this exp, the autoKey val is in 1st col
  int id=rs.getLong(1);
  //now this's a real value of col Id
  System.out.println(id);
}
} catch (SQLException e) {
}

基本上,如果你只想要SEQ.Nextval的值,请尝试不使用Method1,b'cse它只返回RowID引用,你可能会破解你的头部找到使用它的方法,这也适合所有数据类型你试过把它投到了!这可能在MySQL,DB2中正常工作(返回实际的val),但在Oracle中却没有。

重要: 在调试时关闭SQL Developer,Toad或使用相同登录会话的任何客户端进行INSERT。它可能不会影响你每次(调试调用)...直到你发现你的应用程序冻结一段时间没有异常。是的......毫无例外地停止!