Comment obtenir l'ID d'insertion dans JDBC?

385

Je veux INSERTun enregistrement dans une base de données (qui est Microsoft SQL Server dans mon cas) en utilisant JDBC en Java. En même temps, je veux obtenir l'ID d'insertion. Comment puis-je y parvenir à l'aide de l'API JDBC?

Satya
la source
Laissez l' id qui est AutoGenrerated dans la requête 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

Réponses:

650

S'il s'agit d'une clé générée automatiquement, vous pouvez l'utiliser Statement#getGeneratedKeys()pour cela. Vous devez l'appeler de la même manière Statementque celle utilisée pour le INSERT. Vous devez d' abord créer l'instruction à l'aide de Statement.RETURN_GENERATED_KEYSpour informer le pilote JDBC de retourner les clés.

Voici un exemple de 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.");
            }
        }
    }
}

Notez que vous dépendez du pilote JDBC pour savoir s'il fonctionne. Actuellement, la plupart des dernières versions fonctionneront, mais si je ne me trompe pas, le pilote Oracle JDBC est toujours quelque peu gênant avec cela. MySQL et DB2 le supportaient déjà depuis des lustres. PostgreSQL a commencé à le prendre en charge il n'y a pas longtemps. Je ne peux pas commenter MSSQL car je ne l'ai jamais utilisé.

Pour Oracle, vous pouvez appeler un CallableStatementavec une RETURNINGclause ou un SELECT CURRVAL(sequencename)(ou la syntaxe spécifique à DB pour ce faire) directement après la INSERTdans la même transaction pour obtenir la dernière clé générée. Voir aussi cette réponse .

BalusC
la source
4
Il est préférable d'obtenir la valeur suivante dans une séquence avant l'insertion que d'obtenir le currval après l'insertion, car cette dernière peut renvoyer la mauvaise valeur dans un environnement multi-thread (par exemple, n'importe quel conteneur d'application Web). Le pilote JTDS MSSQL prend en charge getGeneratedKeys.
JeeBee
4
(Je dois préciser que j'utilise habituellement Oracle, donc j'ai très peu d'attentes sur les capacités d'un pilote JDBC normalement).
JeeBee
7
Un effet secondaire intéressant de ne PAS définir l'option Statement.RETURN_GENERATED_KEYS est le message d'erreur, qui est complètement obscur "L'instruction doit être exécutée avant d'obtenir des résultats."
Chris Winters
7
La generatedKeys.next()renvoie truesi la base de données a renvoyé une clé générée. Regardez, c'est un ResultSet. Il close()s'agit simplement de libérer des ressources. Sinon, votre base de données en manquera à long terme et votre application se cassera. Vous n'avez qu'à écrire vous-même une méthode utilitaire qui fait la tâche de fermeture. Voir aussi ceci et cette réponse.
BalusC
5
Bonne réponse pour la plupart des bases de données / pilotes. Pour Oracle, cela ne fonctionne pas cependant. Pour Oracle, passez à: connection.prepareStatement (sql, new String [] {"PK column name"});
Darrell Teague
24
  1. Créer une colonne générée

    String generatedColumns[] = { "ID" };
  2. Passez cette colonne créée à votre déclaration

    PreparedStatement stmtInsert = conn.prepareStatement(insertSQL, generatedColumns);
  3. Utiliser un ResultSetobjet pour récupérer les GeneratedKeys on Statement

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

Je frappe Microsoft SQL Server 2008 R2 à partir d'une application basée sur JDBC à un seul thread et je récupère le dernier ID sans utiliser la propriété RETURN_GENERATED_KEYS ou tout PreparedStatement. Ressemble à ceci:

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

Ce billet de blog isole joliment trois options principales de "dernier ID" de SQL Server: http://msjawahar.wordpress.com/2008/01/25/how-to-find-the-last-identity-value-insert-in-the -sql-server / - n'a pas encore eu besoin des deux autres.

ftexperts
la source
4
Le fait que l'application ne comporte qu'un seul thread ne rend pas une condition de concurrence impossible: si deux clients insèrent une ligne et récupèrent l'ID avec votre méthode, cela peut échouer.
11684
Pourquoi voudrais-tu? Je suis juste content de ne pas être le pauvre gazon qui doit déboguer votre code lorsque vous autorisez plusieurs threads!
mjaggard
6

Lorsque vous rencontrez une erreur «Fonction non prise en charge» lors de l'utilisation Statement.RETURN_GENERATED_KEYS, essayez ceci:

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

BATCHIDest l'ID généré automatiquement.

Eitan Rimon
la source
tu veux direBATCHID
MoolsBytheway
Très bonne réponse!!!
Hasitha Jayawardana
3

J'utilise SQLServer 2008, mais j'ai une limitation de développement: je ne peux pas utiliser un nouveau pilote pour cela, je dois utiliser "com.microsoft.jdbc.sqlserver.SQLServerDriver" (je ne peux pas utiliser "com.microsoft.sqlserver.jdbc .SQLServerDriver ").

C'est pourquoi la solution a conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)lancé une java.lang.AbstractMethodError pour moi. Dans cette situation, une solution possible que j'ai trouvée est l'ancienne suggérée par Microsoft: Comment récupérer la valeur @@ IDENTITY à l'aide de 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)
        {
        }
    }
}

Cette solution a fonctionné pour moi!

J'espère que ça aide!

xanblax
la source
1

Au lieu d'un commentaire , je veux juste répondre à un post.


Interface java.sql.PreparedStatement

  1. columnIndexes «Vous pouvez utiliser la fonction prepareStatement qui accepte columnIndexes et l'instruction SQL. Où les indicateurs de colonnes autorisés de columnIndexes sont Statement.RETURN_GENERATED_KEYS 1 ou Statement.NO_GENERATED_KEYS [2], instruction SQL pouvant contenir un ou plusieurs '?' Espaces réservés pour les paramètres IN.

    SYNTAX «

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

    Exemple:

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

  1. columnNames « Liste les noms de colonnes comme 'id', 'uniqueID', .... dans la table cible contenant les clés générées automatiquement qui doivent être renvoyées. Le pilote les ignorera si l'instruction SQL n'est pas une INSERTinstruction.

    SYNTAX «

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

    Exemple:

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

Exemple complet:

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
la source
Est-il sûr d'utiliser cette solution dans un environnement d'exécution multithread?
Le prototype il y a
1

Vous pouvez utiliser le code java suivant pour obtenir un nouvel identifiant inséré.

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);
}
SP
la source
0

Avec NativeQuery d'Hibernate, vous devez renvoyer une ResultList au lieu d'un SingleResult, car Hibernate modifie une requête native

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

comme

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

si vous essayez d'obtenir un seul résultat, ce qui provoque la plupart des bases de données (au moins PostgreSQL) à lancer une erreur de syntaxe. Ensuite, vous pouvez récupérer l'ID résultant de la liste (qui contient généralement exactement un élément).

Balin
la source
0

Il est possible de l'utiliser aussi avec des normales Statement(pas seulement 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
la source
0

Dans mon cas ->

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
la source
Pourtant, je pense que c'est une approche longue pour l'obtenir. Je pense qu'il y aura aussi une solution plus compressée.
TheSagya
-6
Connection cn = DriverManager.getConnection("Host","user","pass");
Statement st = cn.createStatement("Ur Requet Sql");
int ret  = st.execute();
Abdelkhalek Benhoumine
la source
Excusez-moi, mais qu'est-ce que c'est censé être?
MoolsBytheway
1. La createStatementméthode de Connectionn'attend aucun paramètre. 2. La executeméthode from Statementattend une chaîne avec une requête. 3. La executeméthode renvoie: truesi le premier résultat est un ResultSetobjet; falses'il s'agit d'un nombre de mises à jour ou s'il n'y a aucun résultat. docs.oracle.com/javase/7/docs/api/java/sql/…
atilacamurca