Come ottenere l'ID di inserimento in JDBC?


385

Voglio INSERTun record in un database (che è Microsoft SQL Server nel mio caso) usando JDBC in Java. Allo stesso tempo, voglio ottenere l'ID di inserimento. Come posso ottenere questo utilizzando l'API JDBC?


Lasciare l' id che è AutoGenrerated nella query String sql = "INSERT INTO 'yash'.'mytable' ('name') VALUES (?)"; int primkey = 0 ; PreparedStatement pstmt = con.prepareStatement(sql, new String[] { "id" }/*Statement.RETURN_GENERATED_KEYS*/); pstmt.setString(1, name); if (pstmt.executeUpdate() > 0) { java.sql.ResultSet generatedKeys = pstmt.getGeneratedKeys (); if (generatedKeys.next()) primkey = generatedKeys.getInt(1); }
Yash,

Risposte:


650

Se si tratta di una chiave generata automaticamente, è possibile utilizzarla Statement#getGeneratedKeys()per questo. È necessario chiamarlo allo stesso modo Statementdi quello utilizzato per il INSERT. È innanzitutto necessario creare l'istruzione utilizzando Statement.RETURN_GENERATED_KEYSper notificare al driver JDBC di restituire le chiavi.

Ecco un esempio di base:

public void create(User user) throws SQLException {
    try (
        Connection connection = dataSource.getConnection();
        PreparedStatement statement = connection.prepareStatement(SQL_INSERT,
                                      Statement.RETURN_GENERATED_KEYS);
    ) {
        statement.setString(1, user.getName());
        statement.setString(2, user.getPassword());
        statement.setString(3, user.getEmail());
        // ...

        int affectedRows = statement.executeUpdate();

        if (affectedRows == 0) {
            throw new SQLException("Creating user failed, no rows affected.");
        }

        try (ResultSet generatedKeys = statement.getGeneratedKeys()) {
            if (generatedKeys.next()) {
                user.setId(generatedKeys.getLong(1));
            }
            else {
                throw new SQLException("Creating user failed, no ID obtained.");
            }
        }
    }
}

Si noti che si dipende dal driver JDBC sul funzionamento. Attualmente, la maggior parte delle ultime versioni funzionerà, ma se ho ragione, il driver Oracle JDBC è ancora un po 'problematico con questo. MySQL e DB2 lo supportano già da anni. PostgreSQL ha iniziato a supportarlo non molto tempo fa. Non posso commentare MSSQL perché non l'ho mai usato.

Per Oracle, è possibile richiamare a CallableStatementcon una RETURNINGclausola o una SELECT CURRVAL(sequencename)(o qualunque sintassi specifica del DB per farlo) direttamente dopo INSERTla stessa transazione per ottenere l'ultima chiave generata. Vedi anche questa risposta .


4
È meglio ottenere il valore successivo in una sequenza prima dell'inserimento piuttosto che ottenere il valore nominale dopo l'inserimento, poiché quest'ultimo potrebbe restituire il valore errato in un ambiente multi-thread (ad esempio, qualsiasi contenitore di app Web). Il driver MSSQL JTDS supporta getGeneratedKeys.
JeeBee,

4
(dovrebbe chiarire che di solito uso Oracle, quindi ho aspettative molto basse sulle capacità di un driver JDBC normalmente).
JeeBee,

7
Un interessante effetto collaterale della NON impostazione dell'opzione Statement.RETURN_GENERATED_KEYS è il messaggio di errore, che è completamente oscuro "L'istruzione deve essere eseguita prima di poter ottenere qualsiasi risultato."
Chris Winters,

7
I generatedKeys.next()rendimenti truese il DB restituito una chiave generata. Guarda, è un ResultSet. Il close()è solo per liberare risorse. Altrimenti il ​​tuo DB si esaurirà a lungo termine e l'applicazione si interromperà. Devi solo scrivere tu stesso un metodo di utilità che fa il compito di chiusura. Vedi anche questa e questa risposta.
BalusC

5
Risposta corretta per la maggior parte dei database / driver. Per Oracle, tuttavia, non funziona. Per Oracle, passare a: connection.prepareStatement (sql, nuova stringa [] {"nome colonna PK"});
Darrell Teague,

24
  1. Crea colonna generata

    String generatedColumns[] = { "ID" };
  2. Passa questa colonna modificata alla tua dichiarazione

    PreparedStatement stmtInsert = conn.prepareStatement(insertSQL, generatedColumns);
  3. Utilizzare l' ResultSetoggetto per recuperare l'istruzione GeneratedKeys on

    ResultSet rs = stmtInsert.getGeneratedKeys();
    
    if (rs.next()) {
        long id = rs.getLong(1);
        System.out.println("Inserted ID -" + id); // display inserted record
    }
    

8

Sto colpendo Microsoft SQL Server 2008 R2 da un'applicazione basata su JDBC a thread singolo e sto ritirando l'ultimo ID senza utilizzare la proprietà RETURN_GENERATED_KEYS o qualsiasi PreparedStatement. Sembra qualcosa del genere:

private int insertQueryReturnInt(String SQLQy) {
    ResultSet generatedKeys = null;
    int generatedKey = -1;

    try {
        Statement statement = conn.createStatement();
        statement.execute(SQLQy);
    } catch (Exception e) {
        errorDescription = "Failed to insert SQL query: " + SQLQy + "( " + e.toString() + ")";
        return -1;
    }

    try {
        generatedKey = Integer.parseInt(readOneValue("SELECT @@IDENTITY"));
    } catch (Exception e) {
        errorDescription = "Failed to get ID of just-inserted SQL query: " + SQLQy + "( " + e.toString() + ")";
        return -1;
    }

    return generatedKey;
} 

Questo post sul blog isola tre opzioni principali dell'ultimo ID di SQL Server: http://msjawahar.wordpress.com/2008/01/25/how-to-find-the-last-identity-value-inserted-in-the -sql-server / - non sono ancora necessari gli altri due.


4
Il fatto che l'applicazione abbia un solo thread non rende impossibile una race condition: se due client inseriscono una riga e recuperano l'ID con il tuo metodo, potrebbe non riuscire.
11684

Perchè vorresti? Sono contento di non essere la povera zolla che deve eseguire il debug del tuo codice quando consente più thread!
mjaggard,

6

Quando si verifica un errore "Funzionalità non supportata" durante l'utilizzo Statement.RETURN_GENERATED_KEYS, provare questo:

String[] returnId = { "BATCHID" };
String sql = "INSERT INTO BATCH (BATCHNAME) VALUES ('aaaaaaa')";
PreparedStatement statement = connection.prepareStatement(sql, returnId);
int affectedRows = statement.executeUpdate();

if (affectedRows == 0) {
    throw new SQLException("Creating user failed, no rows affected.");
}

try (ResultSet rs = statement.getGeneratedKeys()) {
    if (rs.next()) {
        System.out.println(rs.getInt(1));
    }
    rs.close();
}

Dove si BATCHIDtrova l'id generato automaticamente.


vuoi direBATCHID
MoolsBytheway,

Bella risposta!!!
Hasitha Jayawardana,

3

Sto usando SQLServer 2008, ma ho un limite di sviluppo: non posso usare un nuovo driver per questo, devo usare "com.microsoft.jdbc.sqlserver.SQLServerDriver" (non posso usare "com.microsoft.sqlserver.jdbc .SQLServerDriver ").

Ecco perché la soluzione ha conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)lanciato un java.lang.AbstractMethodError per me. In questa situazione, una possibile soluzione che ho trovato è quella suggerita da Microsoft: Come recuperare il valore di IDENTITÀ @@ usando JDBC

import java.sql.*; 
import java.io.*; 

public class IdentitySample
{
    public static void main(String args[])
    {
        try
        {
            String URL = "jdbc:microsoft:sqlserver://yourServer:1433;databasename=pubs";
            String userName = "yourUser";
            String password = "yourPassword";

            System.out.println( "Trying to connect to: " + URL); 

            //Register JDBC Driver
            Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver").newInstance();

            //Connect to SQL Server
            Connection con = null;
            con = DriverManager.getConnection(URL,userName,password);
            System.out.println("Successfully connected to server"); 

            //Create statement and Execute using either a stored procecure or batch statement
            CallableStatement callstmt = null;

            callstmt = con.prepareCall("INSERT INTO myIdentTable (col2) VALUES (?);SELECT @@IDENTITY");
            callstmt.setString(1, "testInputBatch");
            System.out.println("Batch statement successfully executed"); 
            callstmt.execute();

            int iUpdCount = callstmt.getUpdateCount();
            boolean bMoreResults = true;
            ResultSet rs = null;
            int myIdentVal = -1; //to store the @@IDENTITY

            //While there are still more results or update counts
            //available, continue processing resultsets
            while (bMoreResults || iUpdCount!=-1)
            {           
                //NOTE: in order for output parameters to be available,
                //all resultsets must be processed

                rs = callstmt.getResultSet();                   

                //if rs is not null, we know we can get the results from the SELECT @@IDENTITY
                if (rs != null)
                {
                    rs.next();
                    myIdentVal = rs.getInt(1);
                }                   

                //Do something with the results here (not shown)

                //get the next resultset, if there is one
                //this call also implicitly closes the previously obtained ResultSet
                bMoreResults = callstmt.getMoreResults();
                iUpdCount = callstmt.getUpdateCount();
            }

            System.out.println( "@@IDENTITY is: " + myIdentVal);        

            //Close statement and connection 
            callstmt.close();
            con.close();
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }

        try
        {
            System.out.println("Press any key to quit...");
            System.in.read();
        }
        catch (Exception e)
        {
        }
    }
}

Questa soluzione ha funzionato per me!

Spero che questo possa essere d'aiuto!


1

Invece di un commento , voglio solo rispondere al post.


Interfaccia java.sql.PreparedStatement

  1. columnIndexes «È possibile utilizzare la funzione PreparStatement che accetta columnIndexes e l'istruzione SQL. Dove columnIndexes sono ammessi flag costanti sono Statement.RETURN_GENERATED_KEYS 1 o Statement.NO_GENERATED_KEYS [2], istruzione SQL che può contenere uno o più '?' Segnaposto parametro IN.

    SINTASSI «

    Connection.prepareStatement(String sql, int autoGeneratedKeys)
    Connection.prepareStatement(String sql, int[] columnIndexes)

    Esempio:

    PreparedStatement pstmt = 
        conn.prepareStatement( insertSQL, Statement.RETURN_GENERATED_KEYS );

  1. columnNames « Elenca i columnNames come 'id', 'uniqueID', .... nella tabella di destinazione che contiene le chiavi generate automaticamente che devono essere restituite. Il driver li ignorerà se l'istruzione SQL non è INSERTun'istruzione.

    SINTASSI «

    Connection.prepareStatement(String sql, String[] columnNames)

    Esempio:

    String columnNames[] = new String[] { "id" };
    PreparedStatement pstmt = conn.prepareStatement( insertSQL, columnNames );

Esempio completo:

public static void insertAutoIncrement_SQL(String UserName, String Language, String Message) {
    String DB_URL = "jdbc:mysql://localhost:3306/test", DB_User = "root", DB_Password = "";

    String insertSQL = "INSERT INTO `unicodeinfo`( `UserName`, `Language`, `Message`) VALUES (?,?,?)";
            //"INSERT INTO `unicodeinfo`(`id`, `UserName`, `Language`, `Message`) VALUES (?,?,?,?)";
    int primkey = 0 ;
    try {
        Class.forName("com.mysql.jdbc.Driver").newInstance();
        Connection conn = DriverManager.getConnection(DB_URL, DB_User, DB_Password);

        String columnNames[] = new String[] { "id" };

        PreparedStatement pstmt = conn.prepareStatement( insertSQL, columnNames );
        pstmt.setString(1, UserName );
        pstmt.setString(2, Language );
        pstmt.setString(3, Message );

        if (pstmt.executeUpdate() > 0) {
            // Retrieves any auto-generated keys created as a result of executing this Statement object
            java.sql.ResultSet generatedKeys = pstmt.getGeneratedKeys();
            if ( generatedKeys.next() ) {
                primkey = generatedKeys.getInt(1);
            }
        }
        System.out.println("Record updated with id = "+primkey);
    } catch (InstantiationException | IllegalAccessException | ClassNotFoundException | SQLException e) {
        e.printStackTrace();
    }
}

È sicuro utilizzare questa soluzione in un ambiente di runtime multithread?
The Prototype

1

È possibile utilizzare il seguente codice Java per ottenere il nuovo ID inserito.

ps = con.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
ps.setInt(1, quizid);
ps.setInt(2, userid);
ps.executeUpdate();

ResultSet rs = ps.getGeneratedKeys();
if (rs.next()) {
    lastInsertId = rs.getInt(1);
}

0

Con NativeQuery di Hibernate, è necessario restituire un ResultList anziché un SingleResult, poiché Hibernate modifica una query nativa

INSERT INTO bla (a,b) VALUES (2,3) RETURNING id

piace

INSERT INTO bla (a,b) VALUES (2,3) RETURNING id LIMIT 1

se si tenta di ottenere un singolo risultato, ciò causa la maggior parte dei database (almeno PostgreSQL) che genera un errore di sintassi. Successivamente, è possibile recuperare l'id risultante dall'elenco (che di solito contiene esattamente un elemento).


0

È possibile usarlo anche con quelli normali Statement(non solo PreparedStatement)

Statement statement = conn.createStatement();
int updateCount = statement.executeUpdate("insert into x...)", Statement.RETURN_GENERATED_KEYS);
try (ResultSet generatedKeys = statement.getGeneratedKeys()) {
  if (generatedKeys.next()) {
    return generatedKeys.getLong(1);
  }
  else {
    throw new SQLException("Creating failed, no ID obtained.");
  }
}

0

Nel mio caso ->

ConnectionClass objConnectionClass=new ConnectionClass();
con=objConnectionClass.getDataBaseConnection();
pstmtGetAdd=con.prepareStatement(SQL_INSERT_ADDRESS_QUERY,Statement.RETURN_GENERATED_KEYS);
pstmtGetAdd.setString(1, objRegisterVO.getAddress());
pstmtGetAdd.setInt(2, Integer.parseInt(objRegisterVO.getCityId()));
int addId=pstmtGetAdd.executeUpdate();              
if(addId>0)
{
    ResultSet rsVal=pstmtGetAdd.getGeneratedKeys();
    rsVal.next();
    addId=rsVal.getInt(1);
}

Penso comunque che sia un approccio lungo per ottenerlo. Penso che ci sarà anche una soluzione più compressa.
TheSagya,


-6
Connection cn = DriverManager.getConnection("Host","user","pass");
Statement st = cn.createStatement("Ur Requet Sql");
int ret  = st.execute();

Mi scusi, ma cosa dovrebbe essere?
MoolsBytheway,

1. Il createStatementmetodo da Connectionnon prevede parametri. 2. Il executemetodo Statementprevede una stringa con una query. 3. Il executemetodo restituisce: truese il primo risultato è un ResultSetoggetto; falsese è un conteggio degli aggiornamenti o non ci sono risultati. docs.oracle.com/javase/7/docs/api/java/sql/…
atilacamurca
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.