Bagaimana cara mendapatkan ID penyisipan di JDBC?


385

Saya ingin INSERTmerekam dalam database (yang merupakan Microsoft SQL Server dalam kasus saya) menggunakan JDBC di Jawa. Pada saat yang sama, saya ingin mendapatkan ID insert. Bagaimana saya bisa mencapai ini menggunakan JDBC API?


Tinggalkan id yang AutoGenrerated di 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

Jawaban:


650

Jika ini adalah kunci yang dibuat secara otomatis, maka Anda dapat menggunakannya Statement#getGeneratedKeys()untuk ini. Anda harus menyebutnya sama Statementdengan yang digunakan untuk INSERT. Anda harus terlebih dahulu membuat pernyataan menggunakan Statement.RETURN_GENERATED_KEYSuntuk memberi tahu driver JDBC untuk mengembalikan kunci.

Inilah contoh dasar:

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

Perhatikan bahwa Anda bergantung pada driver JDBC, apakah itu berfungsi. Saat ini, sebagian besar versi terakhir akan berfungsi, tetapi jika saya benar, driver Oracle JDBC masih agak bermasalah dengan ini. MySQL dan DB2 sudah mendukungnya sejak lama. PostgreSQL mulai mendukungnya belum lama ini. Saya tidak dapat berkomentar tentang MSSQL karena saya belum pernah menggunakannya.

Untuk Oracle, Anda dapat memanggil CallableStatementdengan RETURNINGklausa atau SELECT CURRVAL(sequencename)(atau sintaks khusus DB apa pun untuk melakukannya) langsung setelah INSERTdalam transaksi yang sama untuk mendapatkan kunci yang dihasilkan terakhir. Lihat juga jawaban ini .


4
Lebih baik untuk mendapatkan nilai berikutnya secara berurutan sebelum menyisipkan daripada mendapatkan flowval setelah menyisipkan, karena yang terakhir mungkin mengembalikan nilai yang salah di lingkungan multi-ulir (misalnya, wadah aplikasi web apa pun). Driver JTDS MSSQL mendukung getGeneratedKeys.
JeeBee

4
(harus menjelaskan bahwa saya biasanya menggunakan Oracle, sehingga memiliki harapan yang sangat rendah dari kemampuan driver JDBC biasanya)
JeeBee

7
Efek samping yang menarik dari TIDAK mengatur opsi Statement.RETURN_GENERATED_KEYS adalah pesan kesalahan, yang merupakan pesan yang sepenuhnya tidak jelas "Pernyataan harus dieksekusi sebelum hasil apa pun dapat diperoleh."
Chris Winters

7
The generatedKeys.next()pengembalian truejika DB kembali kunci yang dihasilkan. Lihat, ini a ResultSet. The close()hanya untuk sumber informasi gratis. Kalau tidak, DB Anda akan habis dalam jangka panjang dan aplikasi Anda akan rusak. Anda hanya perlu menulis sendiri beberapa metode utilitas yang melakukan tugas penutup. Lihat juga ini dan jawaban ini .
BalusC

5
Jawaban yang benar untuk sebagian besar database / driver. Namun untuk Oracle ini tidak berfungsi. Untuk Oracle, ubah ke: connection.prepareStatement (sql, String baru [] {"PK kolom name"});
Darrell Teague

24
  1. Buat Kolom Yang Dihasilkan

    String generatedColumns[] = { "ID" };
  2. Berikan Kolom yang dibuat ini ke pernyataan Anda

    PreparedStatement stmtInsert = conn.prepareStatement(insertSQL, generatedColumns);
  3. Gunakan ResultSetobjek untuk mengambil GeneratedKeys pada Pernyataan

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

8

Saya memukul Microsoft SQL Server 2008 R2 dari aplikasi berbasis JDBC single-threaded dan menarik kembali ID terakhir tanpa menggunakan properti RETURN_GENERATED_KEYS atau PreparedStatement. Terlihat seperti ini:

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

Posting blog ini dengan baik mengisolasi tiga opsi SQL Server "ID terakhir": http://msjawahar.wordpress.com/2008/01/25/how-to-find-the-Last-identity-value-inserted-in-the -sql-server / - belum membutuhkan dua lainnya.


4
Bahwa aplikasi hanya memiliki satu utas tidak membuat kondisi balapan menjadi tidak mungkin: jika dua klien menyisipkan baris dan mengambil ID dengan metode Anda, itu mungkin gagal.
11684

Mengapa kamu akan? Saya senang saya bukan orang miskin yang harus men-debug kode Anda ketika mengizinkan banyak utas!
mjaggard

6

Saat menemukan kesalahan 'Fitur yang tidak didukung' saat menggunakan Statement.RETURN_GENERATED_KEYS, coba ini:

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

Di mana BATCHIDid yang dibuat secara otomatis.


maksud AndaBATCHID
MoolsBytheway

Jawaban yang bagus !!!
Hasitha Jayawardana

3

Saya menggunakan SQLServer 2008, tetapi saya memiliki batasan pengembangan: Saya tidak dapat menggunakan driver baru untuk itu, saya harus menggunakan "com.microsoft.jdbc.sqlserver.SQLServerDriver" (saya tidak bisa menggunakan "com.microsoft.sqlserver.jdbc .SQLServerDriver ").

Karena itulah solusinya conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)melemparkan java.lang.AbstractMethodError untuk saya. Dalam situasi ini, solusi yang mungkin saya temukan adalah yang lama yang disarankan oleh Microsoft: Cara Mengambil Nilai @@ IDENTITY Menggunakan 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)
        {
        }
    }
}

Solusi ini berhasil untuk saya!

Saya harap ini membantu!


1

Alih-alih komentar , saya hanya ingin menjawab posting.


Antarmuka java.sql.PreparedStatement

  1. columnIndexes «Anda dapat menggunakan fungsi prepStatement yang menerima pernyataan columnIndexes dan SQL. Di mana kolomIndeks diizinkan bendera konstan adalah Statement.RETURN_GENERATED_KEYS 1 atau Statement.NO_GENERATED_KEYS [2], pernyataan SQL yang mungkin mengandung satu atau lebih '?' IN placeholder parameter.

    SINTETIS «

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

    Contoh:

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

  1. Nama Kolom « Sebutkan Nama Kolom seperti 'id', 'uniqueID', .... di tabel target yang berisi kunci yang dibuat secara otomatis yang harus dikembalikan. Pengemudi akan mengabaikannya jika pernyataan SQL bukan INSERTpernyataan.

    SINTETIS «

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

    Contoh:

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

Contoh Lengkap:

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

Apakah aman untuk menggunakan solusi ini dalam lingkungan runtime multithreaded?
Prototipe

1

Anda dapat menggunakan kode java berikut untuk mendapatkan id yang dimasukkan baru.

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

Dengan NativeQuery Hibernate, Anda harus mengembalikan ResultList alih-alih SingleResult, karena Hibernate memodifikasi kueri asli

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

Suka

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

jika Anda mencoba untuk mendapatkan hasil tunggal, yang menyebabkan sebagian besar basis data (setidaknya PostgreSQL) melempar kesalahan sintaksis. Setelah itu, Anda dapat mengambil id yang dihasilkan dari daftar (yang biasanya berisi tepat satu item).


0

Dimungkinkan untuk menggunakannya dengan normal Statementjuga (bukan hanya 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

Dalam kasus saya ->

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

Masih saya pikir ini pendekatan yang panjang untuk mendapatkannya. Saya pikir akan ada solusi yang lebih terkompresi juga.
TheSagya


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

Maaf, tapi apa ini seharusnya?
MoolsBytheway

1. createStatementMetode dari Connectionjangan mengharapkan params. 2. executeMetode dari Statementmengharapkan String dengan Kueri. 3. executeMetode mengembalikan: truejika hasil pertama adalah ResultSetobjek; falseapakah itu jumlah pembaruan atau tidak ada hasil. docs.oracle.com/javase/7/docs/api/java/sql/…
atilacamurca
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.