Como obter o ID da inserção no JDBC?

385

Eu quero INSERTum registro em um banco de dados (que é o Microsoft SQL Server no meu caso) usando JDBC em Java. Ao mesmo tempo, quero obter o ID da inserção. Como conseguir isso usando a API JDBC?

Satya
fonte
Deixe o ID AutoGenrerated na 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

Respostas:

650

Se for uma chave gerada automaticamente, você poderá usá Statement#getGeneratedKeys()-la. Você precisa chamá-lo da mesma Statementforma que está sendo usado para o INSERT. Você primeiro precisa criar a instrução usando Statement.RETURN_GENERATED_KEYSpara notificar o driver JDBC para retornar as chaves.

Aqui está um exemplo 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.");
            }
        }
    }
}

Observe que você depende do driver JDBC para saber se ele funciona. Atualmente, a maioria das últimas versões funcionará, mas se eu estiver correto, o driver Oracle JDBC ainda é um pouco problemático. O MySQL e o DB2 já o suportam há séculos. O PostgreSQL começou a apoiá-lo há pouco tempo. Não posso comentar sobre o MSSQL como nunca o usei.

Para Oracle, você pode chamar a CallableStatementcom uma RETURNINGcláusula ou uma SELECT CURRVAL(sequencename)(ou qualquer sintaxe específica do DB para fazer isso) diretamente após INSERTa mesma transação para obter a última chave gerada. Veja também esta resposta .

BalusC
fonte
4
É melhor obter o próximo valor em uma sequência antes da inserção do que obter o currval após a inserção, porque o último pode retornar o valor errado em um ambiente multithread (por exemplo, qualquer contêiner de aplicativo da web). O driver JTDS MSSQL suporta getGeneratedKeys.
21710 JeeBee
4
(deve esclarecer que eu normalmente uso Oracle, portanto, tenho expectativas muito baixas dos recursos de um driver JDBC normalmente).
JeeBee
7
Um efeito colateral interessante de NÃO definir a opção Statement.RETURN_GENERATED_KEYS é a mensagem de erro, que é completamente obscura "A instrução deve ser executada antes que quaisquer resultados possam ser obtidos."
Chris Winters
7
Os generatedKeys.next()retornos truese o DB devolvida uma chave gerada. Olha, é um ResultSet. O close()é apenas para liberar recursos. Caso contrário, seu banco de dados ficará sem eles a longo prazo e seu aplicativo será interrompido. Você só precisa escrever algum método utilitário que execute a tarefa de fechamento. Veja também esta e esta resposta.
BalusC
5
Resposta correta para a maioria dos bancos de dados / drivers. Para Oracle, isso não funciona no entanto. Para Oracle, altere para: connection.prepareStatement (sql, new String [] {"PK column name"});
Darrell Teague
24
  1. Criar coluna gerada

    String generatedColumns[] = { "ID" };
  2. Passe esta coluna geneada à sua declaração

    PreparedStatement stmtInsert = conn.prepareStatement(insertSQL, generatedColumns);
  3. Use o ResultSetobjeto para buscar as 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
fonte
8

Estou acessando o Microsoft SQL Server 2008 R2 a partir de um aplicativo baseado em JDBC de thread único e retirando o último ID sem usar a propriedade RETURN_GENERATED_KEYS ou qualquer PreparedStatement. Parece algo assim:

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 postagem no blog isola bem três opções principais de "última identificação" do SQL Server: http://msjawahar.wordpress.com/2008/01/25/how-to-find-the-last-identity-value-inserted-in-the -sql-server / - ainda não precisou dos outros dois.

ftexperts
fonte
4
O fato de o aplicativo ter apenas um encadeamento não impossibilita uma condição de corrida: se dois clientes inserem uma linha e recuperam o ID com seu método, isso pode falhar.
11684
Por que você? Estou feliz por não ser o pobre coitado que precisa depurar seu código ao permitir vários threads!
Mjaggard #
6

Ao encontrar um erro 'Recurso não suportado' durante o uso Statement.RETURN_GENERATED_KEYS, tente o seguinte:

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

Onde BATCHIDestá o ID gerado automaticamente.

Eitan Rimon
fonte
você quer dizerBATCHID
MoolsBytheway
Ótima resposta!!!
Hasitha Jayawardana
3

Estou usando o SQLServer 2008, mas tenho uma limitação de desenvolvimento: não consigo usar um novo driver, tenho que usar "com.microsoft.jdbc.sqlserver.SQLServerDriver" (não consigo usar "com.microsoft.sqlserver.jdbc .SQLServerDriver ").

É por isso que a solução conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)lançou um java.lang.AbstractMethodError para mim. Nessa situação, uma solução possível que encontrei é a antiga sugerida pela Microsoft: Como recuperar o valor @@ IDENTITY 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)
        {
        }
    }
}

Esta solução funcionou para mim!

Eu espero que isso ajude!

xanblax
fonte
1

Em vez de um comentário , só quero responder a postagem.


Interface java.sql.PreparedStatement

  1. columnIndexes «Você pode usar a função prepareStatement que aceita columnIndexes e instrução SQL. Onde columnIndexes permitidos sinalizadores constantes são Statement.RETURN_GENERATED_KEYS 1 ou Statement.NO_GENERATED_KEYS [2], instrução SQL que pode conter um ou mais '?' Marcadores de parâmetro IN.

    SYNTAX «

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

    Exemplo:

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

  1. columnNames « Liste os columnNames como 'id', 'uniqueID', .... na tabela de destino que contém as chaves geradas automaticamente que devem ser retornadas. O driver irá ignorá-los se a instrução SQL não for uma INSERTinstrução.

    SYNTAX «

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

    Exemplo:

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

Exemplo 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
fonte
É seguro usar esta solução em um ambiente de tempo de execução multithread?
O protótipo
1

Você pode usar o seguinte código java para obter o novo ID inserido.

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
fonte
0

Com o NativeQuery do Hibernate, você precisa retornar um ResultList em vez de um SingleResult, porque o Hibernate modifica uma consulta nativa

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

gostar

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

se você tentar obter um único resultado, o que fará com que a maioria dos bancos de dados (pelo menos PostgreSQL) gere um erro de sintaxe. Depois, você pode buscar o ID resultante da lista (que geralmente contém exatamente um item).

Balin
fonte
0

É possível usá-lo com o normal Statementtambém (não apenas 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
fonte
0

No meu 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
fonte
Ainda acho que é uma abordagem demorada para obtê-lo. Eu acho que haverá mais solução compactada também.
TheSagya
-6
Connection cn = DriverManager.getConnection("Host","user","pass");
Statement st = cn.createStatement("Ur Requet Sql");
int ret  = st.execute();
Abdelkhalek Benhoumine
fonte
Com licença, mas o que isso deveria ser?
precisa saber é o seguinte
1. O createStatementmétodo de Connectionnão espera nenhum parâmetro. 2. O executemétodo de Statementespera uma String com uma Consulta. 3. O executemétodo retorna: truese o primeiro resultado for um ResultSetobjeto; falsese é uma contagem de atualização ou não há resultados. docs.oracle.com/javase/7/docs/api/java/sql/…
atilacamurca