JDBC PreparedStatement всегда возвращает 1 Как автоматически сгенерированный ключ [дубликат]

этот вопрос уже есть ответ здесь:

у меня этот код пытаюсь вставить запись в базу данных:

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) {}

проблема, хотя запись вставлена правильно, я хочу!--1--> содержать первичный ключ + идентификатор auto_increment только что вставленной записи. Однако по какой-то причине он всегда возвращает " 1 " в качестве идентификатора, возможно, потому, что значение userId равно 1 во время вставок.

мой стол InnoDB. Сначала userId был внешним ключом к другой таблице, с тех пор я удалил внешний ключ и даже индекс в столбце userId, но я все еще получаю 1 как возвращаемое значение.

любые идеи, что я делаю неправильно?

5 ответов


PreparedStatment.executeUpdate()

возвращает:
либо (1) Количество строк для операторов языка обработки данных SQL (DML), либо (2) 0 для операторов SQL, которые ничего не возвращают

вам нужно использовать execute() вместо этого и получить ResultSet С getGeneratedKeys(); он будет содержать нужные вам данные.

правка, чтобы добавить: Я прочитал Ваш вопрос, так как есть поле автоматического приращения в таблице не userId


на принято отвечать Брайан Роуч прав. Я добавляю некоторые мысли и пример с полным кодом.

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 состоит из строк, содержащих только сгенерированные значения ключей.

эта схема позволяет чтобы добавить поведение возврата сгенерированных ключей при сохранении другого желаемого поведения, получите количество затронутых строк.

Пример Кода

вот почти реальный пример, взятый из приложения Java 8, которое очищает данные из канала данных. Я думаю, что в этом контексте полномасштабный пример может быть более полезным, чем минимальный.

мелкие детали... этот код может быть не идеальным, синтаксически или иначе, так как я копирую-вставляю-изменяю реальный исходный код. Я использую тип данных UUID вместо целых чисел как суррогатная мать первичный ключ моей таблицы. Классы CharHelper и DBHelper являются моими собственными, детали которых здесь не важны. The x и y переменные являются заменами значимых данных моего собственного приложения. Мои вызовы регистрации сделаны в SLF4J основы. The 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, задать для результирующего набора сгенерированных ключей. В этом примере мы вставили одну строку и ожидаем возврата одного сгенерированного ключа.

        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;
}

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) {
}

Если вы установили userId имеет автоматическое приращение в вашей базе данных, вы не должны пытаться добавить его самостоятельно. Вы должны вставить NULL, и он будет автоматически увеличиваться для вас! (Ключ к разгадке в названии!)

кроме того, вы не обновляете свою таблицу, вы вставляете в нее. Таким образом, вы не выполняете executeUpdate(). Пытаться...

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

то, что вы получаете, - это уведомление о "строках insetted" (для инструкции INSERT). Мы используем этот метод, чтобы узнать, является ли наш запрос DML успешным или нет. Ниже приведен способ получить автоматически сгенерированный идентификатор с помощью [prepareStatement (yourSQL, Statement.RETURN_GENERATED_KEYS)]. Обратите внимание, что этот метод возвращает вам только rowid ref. Для того чтобы получить фактический вал, пльс ссылаются на Метод 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) {
}

в основном, попробуйте не использовать Method1, если вы просто хотите значение SEQ.Nextval, b'CSE это просто вернуть rowid ref, что вы можете треснул голову найти способ использовать его, который также не подходит для всех типов данных, которые вы пытались бросить его! Это может отлично работать (возвращать фактический val) в MySQL, DB2, но не в Oracle.

важно: Выключите SQL Developer, Toad или любой клиент, который использует один и тот же сеанс входа для вставки при отладке. Это может не влиять на вас каждый раз (вызов отладки)... пока ты не найдешь свою приложения замораживаются без исключения в течение некоторого времени. Да... стой без исключения!