Wie erhalte ich die Einfügungs-ID in JDBC?

385

Ich möchte INSERTeinen Datensatz in einer Datenbank (in meinem Fall Microsoft SQL Server) mit JDBC in Java erstellen. Gleichzeitig möchte ich die Insert-ID erhalten. Wie kann ich dies mit der JDBC-API erreichen?

Satya
quelle
Belassen Sie die ID AutoGenrerated in der Abfrage 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

Antworten:

650

Wenn es sich um einen automatisch generierten Schlüssel handelt, können Sie Statement#getGeneratedKeys()diesen verwenden. Sie müssen es genauso aufrufen Statementwie das, das für das verwendet wird INSERT. Sie zuerst müssen die Anweisung erstellen mit Statement.RETURN_GENERATED_KEYSden JDBC - Treiber zu benachrichtigen , die Schlüssel zurück.

Hier ist ein einfaches Beispiel:

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.");
            }
        }
    }
}

Beachten Sie, dass Sie vom JDBC-Treiber abhängig sind, ob er funktioniert. Derzeit funktionieren die meisten der letzten Versionen, aber wenn ich richtig liege, ist der Oracle JDBC-Treiber immer noch etwas problematisch. MySQL und DB2 unterstützen es bereits seit Ewigkeiten. PostgreSQL hat vor kurzem damit begonnen, es zu unterstützen. Ich kann MSSQL nicht kommentieren, da ich es nie benutzt habe.

Für Oracle können Sie a CallableStatementmit einer RETURNINGKlausel oder einer SELECT CURRVAL(sequencename)(oder einer beliebigen DB-spezifischen Syntax) direkt nach der INSERTin derselben Transaktion aufrufen , um den zuletzt generierten Schlüssel zu erhalten. Siehe auch diese Antwort .

BalusC
quelle
4
Es ist besser, den nächsten Wert in einer Sequenz vor dem Einfügen abzurufen, als den Wert nach dem Einfügen abzurufen, da letzterer in einer Umgebung mit mehreren Threads (z. B. einem beliebigen Web-App-Container) möglicherweise den falschen Wert zurückgibt. Der JTDS MSSQL-Treiber unterstützt getGeneratedKeys.
JeeBee
4
(sollte klarstellen, dass ich normalerweise Oracle verwende, also sehr geringe Erwartungen an die normalen Fähigkeiten eines JDBC-Treibers habe).
JeeBee
7
Ein interessanter Nebeneffekt, wenn die Option Statement.RETURN_GENERATED_KEYS NICHT gesetzt wird, ist die Fehlermeldung, die völlig unklar ist: "Die Anweisung muss ausgeführt werden, bevor Ergebnisse erzielt werden können."
Chris Winters
7
Die generatedKeys.next()zurückkehrt , truewenn die DB zurück einen generierten Schlüssel. Schau, es ist ein ResultSet. Das close()ist nur, um Ressourcen freizugeben. Andernfalls werden Ihre DB auf lange Sicht keine mehr haben und Ihre Anwendung wird kaputt gehen. Sie müssen nur selbst eine Dienstprogrammmethode schreiben, die die Abschlussaufgabe ausführt. Siehe auch diese und diese Antwort.
BalusC
5
Richtige Antwort für die meisten Datenbanken / Treiber. Für Oracle funktioniert dies jedoch nicht. Wechseln Sie für Oracle zu: connection.prepareStatement (SQL, neuer String [] {"PK-Spaltenname"});
Darrell Teague
24
  1. Generierte Spalte erstellen

    String generatedColumns[] = { "ID" };
  2. Übergeben Sie diese geneatisierte Spalte an Ihre Aussage

    PreparedStatement stmtInsert = conn.prepareStatement(insertSQL, generatedColumns);
  3. Verwenden Sie das ResultSetObjekt, um die GeneratedKeys on-Anweisung abzurufen

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

Ich rufe Microsoft SQL Server 2008 R2 von einer JDBC-basierten Single-Thread-Anwendung aus auf und ziehe die letzte ID zurück, ohne die RETURN_GENERATED_KEYS-Eigenschaft oder ein PreparedStatement zu verwenden. Sieht ungefähr so ​​aus:

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

In diesem Blog-Beitrag werden drei Hauptoptionen für die "letzte ID" von SQL Server gut isoliert: http://msjawahar.wordpress.com/2008/01/25/how-to-find-the-last-identity-value-inserted-in-the -sql-server / - hat die beiden anderen noch nicht gebraucht.

ftexperts
quelle
4
Dass die Anwendung nur einen Thread hat, macht eine Race-Bedingung nicht unmöglich: Wenn zwei Clients eine Zeile einfügen und die ID mit Ihrer Methode abrufen, kann dies fehlschlagen.
11684
Warum würdest du? Ich bin nur froh, dass ich nicht der arme Kerl bin, der Ihren Code debuggen muss, wenn er mehrere Threads zulässt!
mjaggard
6

Statement.RETURN_GENERATED_KEYSVersuchen Sie Folgendes, wenn bei der Verwendung der Fehler "Nicht unterstützte Funktion" auftritt :

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

Wo BATCHIDist die automatisch generierte ID?

Eitan Rimon
quelle
du meinstBATCHID
MoolsBytheway
Gute Antwort!!!
Hasitha Jayawardana
3

Ich verwende SQLServer 2008, habe jedoch eine Entwicklungsbeschränkung: Ich kann keinen neuen Treiber dafür verwenden. Ich muss "com.microsoft.jdbc.sqlserver.SQLServerDriver" verwenden (ich kann "com.microsoft.sqlserver.jdbc" nicht verwenden .SQLServerDriver ").

Aus diesem Grund hat die Lösung für mich conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)einen java.lang.AbstractMethodError ausgelöst . In dieser Situation ist eine mögliche Lösung, die ich gefunden habe, die alte von Microsoft vorgeschlagene: So rufen Sie den @ @ IDENTITY-Wert mithilfe von JDBC ab

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

Diese Lösung hat bei mir funktioniert!

Ich hoffe das hilft!

Xanblax
quelle
1

Anstelle eines Kommentars möchte ich nur den Beitrag beantworten.


Schnittstelle java.sql.PreparedStatement

  1. columnIndexes «Sie können die Funktion prepareStatement verwenden, die columnIndexes und SQL-Anweisungen akzeptiert. Wobei columnIndexes zulässige konstante Flags Statement.RETURN_GENERATED_KEYS 1 oder Statement.NO_GENERATED_KEYS [2] sind, SQL-Anweisung, die ein oder mehrere '?' Platzhalter für IN-Parameter.

    SYNTAX "

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

    Beispiel:

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

  1. columnNames « Listen Sie die columnNames wie auf 'id', 'uniqueID', .... in der Zieltabelle, die die automatisch generierten Schlüssel enthält, die zurückgegeben werden sollen. Der Treiber ignoriert sie, wenn die SQL-Anweisung keine INSERTAnweisung ist.

    SYNTAX "

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

    Beispiel:

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

Vollständiges Beispiel:

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();
    }
}
Yash
quelle
Ist es sicher, diese Lösung in einer Multithread-Laufzeitumgebung zu verwenden?
Der Prototyp
1

Sie können den folgenden Java-Code verwenden, um eine neue eingefügte ID zu erhalten.

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);
}
MS
quelle
0

Bei NativeQuery von Hibernate müssen Sie eine ResultList anstelle eines SingleResult zurückgeben, da Hibernate eine native Abfrage ändert

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

mögen

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

Wenn Sie versuchen, ein einzelnes Ergebnis zu erhalten, führt dies dazu, dass die meisten Datenbanken (zumindest PostgreSQL) einen Syntaxfehler auslösen. Anschließend können Sie die resultierende ID aus der Liste abrufen (die normalerweise genau ein Element enthält).

Balin
quelle
0

Es ist möglich, es auch mit normalen zu verwenden Statement(nicht nur 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.");
  }
}
Rogerdpack
quelle
0

In meinem Fall ->

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);
}
TheSagya
quelle
Trotzdem denke ich, dass es ein langwieriger Ansatz ist, es zu bekommen. Ich denke, es wird auch mehr komprimierte Lösungen geben.
TheSagya
-6
Connection cn = DriverManager.getConnection("Host","user","pass");
Statement st = cn.createStatement("Ur Requet Sql");
int ret  = st.execute();
Abdelkhalek Benhoumine
quelle
Entschuldigung, aber was soll das sein?
MoolsBytheway
1. Die createStatementMethode von Connectionerwartet keine Parameter. 2. Die executeMethode von Statementerwartet einen String mit einer Abfrage. 3. Die executeMethode gibt Folgendes zurück: trueWenn das erste Ergebnis ein ResultSetObjekt ist; falseWenn es sich um eine Aktualisierungsanzahl handelt oder keine Ergebnisse vorliegen. docs.oracle.com/javase/7/docs/api/java/sql/…
atilacamurca