¿Cómo obtener la ID de inserción en JDBC?

385

Quiero INSERTun registro en una base de datos (que es Microsoft SQL Server en mi caso) usando JDBC en Java. Al mismo tiempo, quiero obtener la ID de inserción. ¿Cómo puedo lograr esto usando la API JDBC?

Satya
fuente
Deje la identificación que está AutoGenrerated en la consulta 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

Respuestas:

650

Si se trata de una clave generada automáticamente, puede usarla Statement#getGeneratedKeys()para esto. Debe llamarlo igual Statementque el que se está utilizando para INSERT. Primero debe crear la declaración utilizando Statement.RETURN_GENERATED_KEYSpara notificar al controlador JDBC que devuelva las claves.

Aquí hay un ejemplo básico:

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

Tenga en cuenta que depende del controlador JDBC para saber si funciona. Actualmente, la mayoría de las últimas versiones funcionarán, pero si estoy en lo cierto, el controlador Oracle JDBC sigue siendo un poco problemático con esto. MySQL y DB2 ya lo soportaron por años. PostgreSQL comenzó a admitirlo no hace mucho tiempo. No puedo comentar sobre MSSQL ya que nunca lo he usado.

Para Oracle, puede invocar a CallableStatementcon una RETURNINGcláusula o un SELECT CURRVAL(sequencename)(o cualquier sintaxis específica de DB para hacerlo) directamente después INSERTde la misma transacción para obtener la última clave generada. Ver también esta respuesta .

BalusC
fuente
44
Es mejor obtener el siguiente valor en una secuencia antes de la inserción que obtener el currval después de la inserción, ya que este último puede devolver el valor incorrecto en un entorno de subprocesos múltiples (por ejemplo, cualquier contenedor de aplicaciones web). El controlador JTDS MSSQL admite getGeneratedKeys.
JeeBee
44
(Debería aclarar que generalmente uso Oracle, por lo que normalmente tengo muy pocas expectativas de las capacidades de un controlador JDBC).
JeeBee
77
Un efecto secundario interesante de NO establecer la opción Statement.RETURN_GENERATED_KEYS es el mensaje de error, que es completamente oscuro "La declaración debe ejecutarse antes de que se puedan obtener resultados".
Chris Winters
77
Los generatedKeys.next()rendimientos truesi el DB devolvió una clave generada. Mira, es un ResultSet. El close()es solo para liberar recursos. De lo contrario, su base de datos se quedará sin ellos a largo plazo y su aplicación se romperá. Solo tiene que escribir algún método de utilidad que haga la tarea de cierre. Vea también esto y esta respuesta.
BalusC
55
Respuesta correcta para la mayoría de las bases de datos / controladores. Sin embargo, para Oracle esto no funciona. Para Oracle, cambie a: connection.prepareStatement (sql, new String [] {"PK column name"});
Darrell Teague el
24
  1. Crear columna generada

    String generatedColumns[] = { "ID" };
  2. Pase esta columna generada a su estado de cuenta

    PreparedStatement stmtInsert = conn.prepareStatement(insertSQL, generatedColumns);
  3. Use el ResultSetobjeto para obtener las claves generadas en la instrucción

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

Estoy accediendo a Microsoft SQL Server 2008 R2 desde una aplicación basada en JDBC de un solo subproceso y retirando el último ID sin usar la propiedad RETURN_GENERATED_KEYS o cualquier PreparedStatement. Se ve algo como esto:

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

Esta publicación de blog aísla muy bien tres opciones principales de "último ID" de SQL Server: http://msjawahar.wordpress.com/2008/01/25/how-to-find-the-last-identity-value-inserted-in-the -sql-server / - aún no he necesitado los otros dos.

ftexperts
fuente
44
Que la aplicación tenga solo un subproceso no hace imposible una condición de carrera: si dos clientes insertan una fila y recuperan la ID con su método, puede fallar.
11684
¿Por que lo harias? ¡Me alegra no ser el pobre idiota que tiene que depurar su código cuando permite múltiples hilos!
mjaggard
6

Cuando encuentre un error de 'Función no compatible' mientras lo usa Statement.RETURN_GENERATED_KEYS, intente esto:

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

¿Dónde BATCHIDestá la identificación generada automáticamente?

Eitan Rimon
fuente
quieres decirBATCHID
MoolsBytheway
¡¡¡Gran respuesta!!!
Hasitha Jayawardana
3

Estoy usando SQLServer 2008, pero tengo una limitación de desarrollo: no puedo usar un nuevo controlador, tengo que usar "com.microsoft.jdbc.sqlserver.SQLServerDriver" (No puedo usar "com.microsoft.sqlserver.jdbc .SQLServerDriver ").

Es por eso que la solución conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)arrojó un java.lang.AbstractMethodError para mí. En esta situación, una posible solución que encontré es la anterior sugerida por Microsoft: Cómo recuperar el valor de @@ IDENTITY utilizando 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)
        {
        }
    }
}

¡Esta solución funcionó para mí!

¡Espero que esto ayude!

xanblax
fuente
1

En lugar de un comentario , solo quiero responder la publicación.


Interfaz java.sql.PreparedStatement

  1. columnIndexes «Puede usar la función prepareStatement que acepta columnIndexes y la declaración SQL. Donde columnIndexes permitieron indicadores constantes son Statement.RETURN_GENERATED_KEYS 1 o Statement.NO_GENERATED_KEYS [2], instrucción SQL que puede contener uno o más '?' Marcadores de posición de parámetros IN.

    SINTAXIS «

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

    Ejemplo:

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

  1. columnNames « Listar los columnNames como 'id', 'uniqueID', .... en la tabla de destino que contiene las claves generadas automáticamente que deben devolverse. El controlador los ignorará si la declaración SQL no es una INSERTdeclaración.

    SINTAXIS «

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

    Ejemplo:

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

Ejemplo 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();
    }
}
Yash
fuente
¿Es seguro usar esta solución en un entorno de ejecución multiproceso?
El prototipo hace
1

Puede usar el siguiente código Java para obtener una nueva identificación insertada.

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);
}
em
fuente
0

Con NativeQuery de Hibernate, debe devolver una lista de resultados en lugar de un resultado único, porque Hibernate modifica una consulta nativa

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

me gusta

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

si intenta obtener un único resultado, lo que hace que la mayoría de las bases de datos (al menos PostgreSQL) arrojen un error de sintaxis. Luego, puede buscar la identificación resultante de la lista (que generalmente contiene exactamente un elemento).

Balin
fuente
0

Es posible usarlo también con normales Statement(no 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.");
  }
}
rogerdpack
fuente
0

En mi 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);
}
TheSagya
fuente
Aún así creo que es un enfoque largo para conseguirlo. Creo que habrá más solución comprimida también.
TheSagya
-6
Connection cn = DriverManager.getConnection("Host","user","pass");
Statement st = cn.createStatement("Ur Requet Sql");
int ret  = st.execute();
Abdelkhalek Benhoumine
fuente
Disculpe, pero ¿qué se supone que es esto?
MoolsBytheway
1. El createStatementmétodo de Connectionno espere ningún parámetro. 2. El executemétodo de Statementespera una cadena con una consulta. 3. El executemétodo devuelve: truesi el primer resultado es un ResultSetobjeto; falsesi es un recuento de actualizaciones o no hay resultados. docs.oracle.com/javase/7/docs/api/java/sql/…
atilacamurca