430

I want to INSERT a record in a database (which is Microsoft SQL Server in my case) using JDBC in Java. At the same time, I want to obtain the insert ID. How can I achieve this using JDBC API?

Satya
  • 8,146
  • 9
  • 38
  • 43
  • Leave the **id** which is AutoGenrerated in the 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();](http://docs.oracle.com/javase/6/docs/api/java/sql/Statement.html#getGeneratedKeys%28%29) `if (generatedKeys.next()) primkey = generatedKeys.getInt(1); }` – Yash Nov 23 '15 at 11:40
  • Just a note for everyone. You can only get Generated Keys with AUTO INC type. UUID or char or other types which use defaults will not work with MSSQL. – sproketboy Feb 03 '21 at 14:04

15 Answers15

744

If it is an auto generated key, then you can use Statement#getGeneratedKeys() for this. You need to call it on the same Statement as the one being used for the INSERT. You first need to create the statement using Statement.RETURN_GENERATED_KEYS to notify the JDBC driver to return the keys.

Here's a basic example:

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

Note that you're dependent on the JDBC driver as to whether it works. Currently, most of the last versions will work, but if I am correct, Oracle JDBC driver is still somewhat troublesome with this. MySQL and DB2 already supported it for ages. PostgreSQL started to support it not long ago. I can't comment about MSSQL as I've never used it.

For Oracle, you can invoke a CallableStatement with a RETURNING clause or a SELECT CURRVAL(sequencename) (or whatever DB-specific syntax to do so) directly after the INSERT in the same transaction to obtain the last generated key. See also this answer.

rogerdpack
  • 62,887
  • 36
  • 269
  • 388
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • 5
    It's better to get the next value in a sequence before the insert than to get the currval after the insert, because the latter might return the wrong value in a multi-threaded environment (e.g., any web app container). The JTDS MSSQL driver supports getGeneratedKeys. – JeeBee Dec 16 '09 at 15:44
  • 5
    (should clarify that I usually use Oracle, so have very low expectations of a JDBC driver's capabilities normally). – JeeBee Dec 16 '09 at 15:45
  • 2
    @JeeBee: as far as I know this doesn't apply if you're using the *same* statement (inside the same transaction). IIRC Hibernate also works that way. – BalusC Dec 16 '09 at 16:23
  • 8
    An interesting side-effect of NOT setting the Statement.RETURN_GENERATED_KEYS option is the error message, which is the completely obscure "The statement must be executed before any results can be obtained." – Chris Winters Mar 03 '11 at 16:15
  • @BalusC Nice Post. Very Helpful. I just want to ask what's the use of generatedKeys.next(). and why the close(connection, preparedStatement, generatedKeys); is not working. And Why need to close these 3? Thank you. :) – newbie Mar 09 '11 at 18:48
  • 7
    The `generatedKeys.next()` returns `true` if the DB returned a generated key. Look, it's a `ResultSet`. The `close()` is just to free resources. Otherwise your DB will run out of them on long run and your application will break. You just have to write up some utility method yourself which does the closing task. See also [this](http://stackoverflow.com/questions/3148092/java-jdbc-mysql-connector-how-to-resolve-disconnection-after-a-long-idle-time/3148857#3148857) and [this](http://stackoverflow.com/questions/2313197/jdbc-mysql-connection-pooling-practices/2313262#2313262) answer. – BalusC Mar 09 '11 at 18:55
  • @BalusC thx for this helpful post. I have a question, if you set autocommit(false) immediately after the "try" and then set it back to true in "finally", would you have problems by concurrent threads? i mean if thread 1 generated key "X" , will thread 2 generate a new key other than"X" even if thread 1 has not yet committed? – ccot Feb 01 '12 at 17:58
  • Great! Thanks for share. As aways, The Oracle is in diferent way of all the rest ( like IE ) ... – Magno C Feb 06 '13 at 15:53
  • @ChrisWinters Thanks... your comment saved me a headache! I was getting the damn error :P – Andres F. Jun 12 '13 at 18:43
  • 1
    If you have an insert with an ON DUPLICATE KEY clause, and there is a duplicate key, is there any way to get back the id/key that was duplicated? or do I have to do my own manual query? getGeneratedKeys() will not return as a new insert wasn't actually done. – Mr Zorn Jul 31 '13 at 15:48
  • @BalusC I am inserting GUID's into my database table. `rs.getString(1)` gives me null back. How can I fix that? – Niklas Mar 12 '14 at 09:22
  • @BalusC: great answer. I am looking for a solution to insert rows in join-tables in JDBC. I have a detailed post here. please share your expertise. http://stackoverflow.com/questions/25920251/how-to-automatically-insert-foreign-key-references-in-tables-in-mysql-or-jdbc/25920586#25920586 – brain storm Sep 18 '14 at 19:26
  • 5
    Correct answer for most databases/drivers. For Oracle this does not work however. For Oracle, change to: connection.prepareStatement(sql,new String[] {"PK column name"}); – Darrell Teague Oct 13 '14 at 13:37
  • Doesn't need to throw `SQLException` – Roman C Oct 15 '15 at 20:22
  • The only column returned in the `getGeneratedKeys()` is a `ROWID` - oracle 11. – AlikElzin-kilaka Apr 11 '16 at 08:31
  • @JeeBee `It's better to get the next value in a sequence before the insert than to get the currval after the insert` It makes sense but I didn't understand this properly. I am trying to get id using `getGeneratedKeys()`. How do I get `next value in a sequence before the insert ` ?? – kittu Mar 25 '17 at 21:49
  • @DarrellTeague I think that your answer is more generic than the proposed – deFreitas Sep 13 '17 at 19:51
  • For PreparedStatement you should use `PreparedStatement.RETURN_GENERATED_KEYS` instead of `Statement.RETURN_GENERATED_KEYS`. – Kamil Aug 23 '18 at 14:33
  • @Kamil: Nope, `PreparedStatement extends Statement`. That constant is actually defined over there in `Statement`. – BalusC Aug 24 '18 at 15:36
  • @BalusC what if the attribute that I want to retrieve is not key? Usually we don't want to directly expose the primary key to the end user, so in my table upon update some triggers will create a secondary id for display (i.e. in the URL). Is there a way to retrieve this automatically like that way you suggest above? I know I can query DB next using the retrieved PK to obtain the secondary id, but I'm curious if there is any better way? – torez233 Nov 26 '21 at 05:41
  • @torez233: use a stored procedure / callable statement instead. – BalusC Nov 26 '21 at 10:27
  • what if we had a batch insertion? Is it possible to get the list of the generated keys? – Soheil Pourbafrani Aug 28 '22 at 20:22
  • Is there a way to know the name of the column(s) which the key(s) were generated for? `ResultSetMetaData#getColumnName` only returns "GENERATED_KEYS". – Cardinal System Dec 14 '22 at 21:07
29
  1. Create Generated Column

    String generatedColumns[] = { "ID" };
    
  2. Pass this geneated Column to your statement

    PreparedStatement stmtInsert = conn.prepareStatement(insertSQL, generatedColumns);
    
  3. Use ResultSet object to fetch the GeneratedKeys on Statement

    ResultSet rs = stmtInsert.getGeneratedKeys();
    
    if (rs.next()) {
        long id = rs.getLong(1);
        System.out.println("Inserted ID -" + id); // display inserted record
    }
    
Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121
Harsh Maheswari
  • 467
  • 5
  • 3
12

When encountering an 'Unsupported feature' error while using Statement.RETURN_GENERATED_KEYS, try this:

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

Where BATCHID is the auto generated id.

Miss Chanandler Bong
  • 4,081
  • 10
  • 26
  • 36
Eitan Rimon
  • 641
  • 7
  • 13
9

I'm hitting Microsoft SQL Server 2008 R2 from a single-threaded JDBC-based application and pulling back the last ID without using the RETURN_GENERATED_KEYS property or any PreparedStatement. Looks something like this:

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

This blog post nicely isolates three main SQL Server "last ID" options: http://msjawahar.wordpress.com/2008/01/25/how-to-find-the-last-identity-value-inserted-in-the-sql-server/ - haven't needed the other two yet.

Community
  • 1
  • 1
ftexperts
  • 690
  • 7
  • 8
  • 5
    That the application has only one thread doesn't make a race condition impossible: if two clients insert a row and retrieve the ID with your method, it may fail. – 11684 Mar 11 '13 at 08:54
  • Why would you? I'm just glad I'm not the poor sod who has to debug your code when allowing multiple threads! – mjaggard Sep 03 '13 at 13:32
  • @11684 yes you are right. Some drivers just don't give the ID via `statement.getGeneratedKeys()`, which makes this attempt "understandable". However supplying the ID(s) during the prepareStatement solves this (e.g. `preapareStatement(query, new String[] {insertIdColumnName})`). See @Yash's slightly underrated answer for more details. – Levite Mar 03 '22 at 12:53
7

Instead of a comment, I just want to answer post.


Interface java.sql.PreparedStatement

  1. columnIndexes « You can use prepareStatement function that accepts columnIndexes and SQL statement. Where columnIndexes allowed constant flags are Statement.RETURN_GENERATED_KEYS1 or Statement.NO_GENERATED_KEYS[2], SQL statement that may contain one or more '?' IN parameter placeholders.

    SYNTAX «

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

    Example:

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

  1. columnNames « List out the columnNames like 'id', 'uniqueID', .... in the target table that contain the auto-generated keys that should be returned. The driver will ignore them if the SQL statement is not an INSERT statement.

    SYNTAX «

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

    Example:

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

Full Example:

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
  • 9,250
  • 2
  • 69
  • 74
  • 1
    Is it safe to use this solution in a multithreaded runtime environment? – The Prototype Jun 14 '20 at 16:55
  • This deserves way more upvotes!! It solves returning of IDs even for older drivers - no need to use `@@IDENTIY` (when supplying a `String Array` of requested IDs). – Levite Mar 03 '22 at 12:44
3

I'm using SQLServer 2008, but I have a development limitation: I cannot use a new driver for it, I have to use "com.microsoft.jdbc.sqlserver.SQLServerDriver" (I cannot use "com.microsoft.sqlserver.jdbc.SQLServerDriver").

That's why the solution conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS) threw a java.lang.AbstractMethodError for me. In this situation, a possible solution I found is the old one suggested by Microsoft: How To Retrieve @@IDENTITY Value Using 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)
        {
        }
    }
}

This solution worked for me!

I hope this helps!

xanblax
  • 276
  • 2
  • 5
  • Try supplying `String[]` array of id names you want, instead of `RETURN_GENERATED_KEYS`. This should suddenly give you a valid ResultSet and the ID via getInt(1) therein. – Levite Mar 03 '22 at 12:48
3

You can use following java code to get new inserted id.

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);
}
Miss Chanandler Bong
  • 4,081
  • 10
  • 26
  • 36
2

It is possible to use it with normal Statement's as well (not just 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
  • 62,887
  • 36
  • 269
  • 388
1

With Hibernate's NativeQuery, you need to return a ResultList instead of a SingleResult, because Hibernate modifies a native query

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

like

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

if you try to get a single result, which causes most databases (at least PostgreSQL) to throw a syntax error. Afterwards, you may fetch the resulting id from the list (which usually contains exactly one item).

NightOwl888
  • 55,572
  • 24
  • 139
  • 212
Balin
  • 81
  • 3
1

Most others have suggested to use JDBC API for this, but personally, I find it quite painful to do with most drivers. When in fact, you can just use a native T-SQL feature, the OUTPUT clause:

try (
    Statement s = c.createStatement();
    ResultSet rs = s.executeQuery(
        """
        INSERT INTO t (a, b)
        OUTPUT id
        VALUES (1, 2)
        """
    );
) {
    while (rs.next())
        System.out.println("ID = " + rs.getLong(1));
}

This is the simplest solution for SQL Server as well as a few other SQL dialects (e.g. Firebird, MariaDB, PostgreSQL, where you'd use RETURNING instead of OUTPUT).

I've blogged about this topic more in detail here.

Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
0

In my case ->

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);
}
KarlR
  • 1,545
  • 12
  • 28
TheSagya
  • 35
  • 11
  • Still I think it's lengthy approach to get it. I think there will be more compressed solution also. – TheSagya Dec 31 '18 at 10:12
0

If you are using Spring JDBC, you can use Spring's GeneratedKeyHolder class to get the inserted ID.

See this answer... How to get inserted id using Spring Jdbctemplate.update(String sql, obj...args)

Rob Breidecker
  • 604
  • 1
  • 7
  • 12
0

If you are using JDBC (tested with MySQL) and you just want the last inserted ID, there is an easy way to get it. The method I'm using is the following:

public static Integer insert(ConnectionImpl connection, String insertQuery){

    Integer lastInsertId = -1;
    try{
        final PreparedStatement ps = connection.prepareStatement(insertQuery);
        ps.executeUpdate(insertQuery);
        final com.mysql.jdbc.PreparedStatement psFinal = (com.mysql.jdbc.PreparedStatement) ps;
        lastInsertId = (int) psFinal.getLastInsertID();
        connection.close();
    } catch(SQLException ex){
        System.err.println("Error: "+ex);
    }

    return lastInsertId;
}

Also, (and just in case) the method to get the ConnectionImpl is the following:

public static ConnectionImpl getConnectionImpl(){
    ConnectionImpl conexion = null;

    final String dbName = "database_name";
    final String dbPort = "3306";
    final String dbIPAddress = "127.0.0.1";
    final String connectionPath = "jdbc:mysql://"+dbIPAddress+":"+dbPort+"/"+dbName+"?autoReconnect=true&useSSL=false";
    
    final String dbUser = "database_user";
    final String dbPassword = "database_password";
    try{
        conexion = (ConnectionImpl) DriverManager.getConnection(connectionPath, dbUser, dbPassword);
    }catch(SQLException e){
        System.err.println(e);
    }
    
    return conexion;
}

Remember to add the connector/J to the project referenced libraries.

In my case, the connector/J version is the 5.1.42. Maybe you will have to apply some changes to the connectionPath if you want to use a more modern version of the connector/J such as with the version 8.0.28.

In the file, remember to import the following resources:

import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import com.mysql.jdbc.ConnectionImpl;

Hope this will be helpful.

Pol
  • 454
  • 2
  • 4
  • 12
0

You can use executeQuery(query) function

and add RETURNING * clause to your query instead * you can specify column name

For example

create table first

CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL);

create query

val query: String = "INSERT INTO users (name) VALUES ("Bob"), ("Makr"), ("Adam") RETURNING id;"

apply query

val statement = connection.prepareStatement(query)
val result = statement.executeQuery()
val keys = buildList {
    while (result.next()) { add(result.getLong(1)) }
}

keys is your result. So you can return any column set or full object

-6
Connection cn = DriverManager.getConnection("Host","user","pass");
Statement st = cn.createStatement("Ur Requet Sql");
int ret  = st.execute();
Dave2e
  • 22,192
  • 18
  • 42
  • 50
  • Excuse me, but what this is supposed to be ? – moolsbytheway Apr 03 '18 at 20:26
  • 1. The `createStatement` method from `Connection` do not expect any params. 2. The `execute` method from `Statement` expects a String with a Query. 3. The `execute` method returns: `true` if the first result is a `ResultSet` object; `false` if it is an update count or there are no results. https://docs.oracle.com/javase/7/docs/api/java/sql/Statement.html#execute(java.lang.String) – atilacamurca May 17 '18 at 12:10