Як отримати ідентифікатор вставки в JDBC?


385

Я хочу INSERTзробити запис у базі даних (що в моєму випадку є Microsoft SQL Server) за допомогою JDBC на Java. У той же час я хочу отримати ідентифікатор вставки. Як я можу досягти цього за допомогою API JDBC?


Залиште ідентифікатор, який автоматично заповнений у 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); }
Яш

Відповіді:


650

Якщо це автоматично створений ключ, то ви можете використовувати Statement#getGeneratedKeys()для цього. Вам потрібно викликати його так само, Statementяк і те, що використовується для INSERT. Спочатку потрібно створити оператор, використовуючи Statement.RETURN_GENERATED_KEYSсповіщення драйвера JDBC про повернення ключів.

Ось основний приклад:

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

Зауважте, що ви залежать від драйвера JDBC щодо того, чи працює він. В даний час більшість останніх версій буде працювати, але якщо я правильно, драйвер Oracle JDBC все ще дещо турбує це. MySQL і DB2 вже підтримували його протягом століть. PostgreSQL не так давно почав підтримувати його. Я не можу коментувати MSSQL, оскільки ніколи не використовував його.

Для Oracle, ви можете викликати CallableStatementза допомогою RETURNINGпункту або SELECT CURRVAL(sequencename)(або будь-який інший БД специфічного синтаксису , щоб зробити це) , безпосередньо після того , INSERTв тій же транзакції , щоб отримати останній згенерований ключ. Дивіться також цю відповідь .


4
Краще отримати наступне значення в послідовності перед вставкою, ніж отримувати currval після вставки, тому що остання може повернути неправильне значення у багатопотоковому середовищі (наприклад, будь-який контейнер веб-додатків). Драйвер JTDS MSSQL підтримує getGeneratedKeys.
JeeBee

4
(Слід уточнити, що я зазвичай використовую Oracle, тому в мене дуже низькі очікування щодо можливостей драйвера JDBC).
JeeBee

7
Цікавим побічним ефектом НЕ встановлення Заявки.RETURN_GENERATED_KEYS опцією є повідомлення про помилку, яке є абсолютно незрозумілим "Заява повинна бути виконана, перш ніж можна отримати будь-які результати".
Кріс Вінтерс

7
В generatedKeys.next()повертається , trueякщо DB повертається згенерований ключ. Подивіться, це а ResultSet. Справа close()лише у тому, щоб звільнити ресурси. Інакше у вашої БД буде вичерпано їх довгостроково, і ваша програма порушиться. Вам просто потрібно написати якийсь корисний метод самостійно, який виконує завдання закриття. Дивіться також цю і цю відповідь.
BalusC

5
Правильна відповідь для більшості баз даних / драйверів. Однак для Oracle це не працює. Для Oracle змініть на: connection.prepareStatement (sql, новий String [] {"Назва стовпця PK"});
Даррелл Тейг

24
  1. Створіть генеровану колонку

    String generatedColumns[] = { "ID" };
  2. Передайте цей сформований стовпець до вашої заяви

    PreparedStatement stmtInsert = conn.prepareStatement(insertSQL, generatedColumns);
  3. Використовуйте ResultSetоб'єкт для отримання згенерованих ключів у заяві

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

8

Я натискаю на Microsoft SQL Server 2008 R2 з однопотокової програми на базі JDBC і відтягую останній ідентифікатор, не використовуючи властивість RETURN_GENERATED_KEYS або будь-який підготовлений стан. Виглядає приблизно так:

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

Ця публікація в блозі добре виділяє три основні параметри "останнього ідентифікатора" SQL Server: http://msjawahar.wordpress.com/2008/01/25/how-to-find-the-last-identity-value-inserted-in-the -sql-server / - інші два ще не потрібні.


4
Те, що у додатку є лише один потік, не робить умовою гонки неможливим: якщо два клієнти вставляють рядок і отримують ідентифікатор за допомогою вашого методу, він може не вдатися.
11684

Чому б ти? Я просто радий, що я не поганий дерновик, якому доводиться налагоджувати ваш код при дозволі декількох потоків!
mjaggard

6

Під час використання помилки "Непідтримувана функція" під час використання Statement.RETURN_GENERATED_KEYSспробуйте:

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

Де BATCHIDавтоматично створений ідентифікатор.


ви маєте на увазіBATCHID
MoolsBytheway

Чудова відповідь !!!
Хасіта Джаявардана

3

Я використовую SQLServer 2008, але у мене є обмеження в розробці: я не можу використовувати для нього новий драйвер, я повинен використовувати "com.microsoft.jdbc.sqlserver.SQLServerDriver" (я не можу використовувати "com.microsoft.sqlserver.jdbc .SQLServerDriver ").

Ось чому рішення conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)кинуло для мене java.lang.Ab абстрактMethodError . У цій ситуації можливим рішенням, який я знайшов, є старе, запропоноване Microsoft: Як отримати значення @@ IDENTITY за допомогою 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)
        {
        }
    }
}

Це рішення спрацювало на мене!

Я сподіваюся, що це допомагає!


1

Замість коментаря я просто хочу відповісти на повідомлення.


Інтерфейс java.sql.PreparedStatement

  1. columnIndexes «Ви можете використовувати функцію PrepaStatement, яка приймає columnIndexes і оператор SQL. Якщо постійні прапорці stolIndexes дозволені постійними прапорцями, є Statement.RETURN_GENERATED_KEYS 1 або Statement.NO_GENERATED_KEYS [2], оператор SQL, який може містити один або більше "?" IN заповнення параметрів.

    SYNTAX «

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

    Приклад:

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

  1. columnNames « Перерахуйте назви стовпців, як 'id', 'uniqueID', .... в цільовій таблиці, що містить автоматично згенеровані ключі, які слід повернути. Драйвер буде ігнорувати їх, якщо оператор SQL не є INSERTоператором.

    SYNTAX «

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

    Приклад:

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

Повний приклад:

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

Чи безпечно використовувати це рішення у багатопотокових умовах виконання?
Прототип

1

Ви можете використовувати наступний код Java для отримання нового вставленого ідентифікатора.

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

За допомогою NativeQuery Hibernate потрібно повернути ResultList замість SingleResult, оскільки Hibernate модифікує власний запит

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

подібно до

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

якщо ви спробуєте отримати єдиний результат, через який більшість баз даних (принаймні PostgreSQL) кидають синтаксичну помилку. Згодом ви можете отримати отриманий ідентифікатор зі списку (який зазвичай містить точно один елемент).


0

Можна використовувати його і з нормальними Statement(не тільки 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

У моєму випадку ->

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


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

Вибачте, але що це повинно бути?
MoolsBytheway

1. createStatementМетод від Connectionне очікуйте ніяких парам. 2. executeМетод Statementочікує рядок із запитом. 3. executeМетод повертає: trueякщо перший результат є ResultSetоб’єктом; falseякщо це кількість оновлень або немає результатів. docs.oracle.com/javase/7/docs/api/java/sql/…
atilacamurca
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.