如何在 JDBC 中获取插入 ID?

Posted

技术标签:

【中文标题】如何在 JDBC 中获取插入 ID?【英文标题】:How to get the insert ID in JDBC? 【发布时间】:2010-12-27 06:35:54 【问题描述】:

我想在 Java 中使用 JDBC INSERT 数据库(在我的例子中是 Microsoft SQL Server)中的一条记录。同时,我想获取插入ID。如何使用 JDBC API 实现这一点?

【问题讨论】:

留下查询中自动生成的 id 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); 仅供大家参考。您只能获得 AUTO INC 类型的 Generated Keys。 UUID 或 char 或其他使用默认值的类型将不适用于 MSSQL。 【参考方案1】:

如果它是自动生成的密钥,那么您可以为此使用Statement#getGeneratedKeys()。您需要在与INSERT 相同的Statement 上调用它。您首先需要创建使用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,您可以在同一事务中直接在 INSERT 之后使用 RETURNING 子句或 SELECT CURRVAL(sequencename)(或任何特定于数据库的语法)调用 CallableStatement,以获得最后生成的钥匙。另见this answer。

【讨论】:

在插入之前获取序列中的下一个值比在插入之后获取 currval 更好,因为后者可能在多线程环境中返回错误值(例如,任何 Web 应用容器)。 JTDS MSSQL 驱动程序支持 getGeneratedKeys。 (应该澄清一下,我通常使用Oracle,因此通常对JDBC驱动程序的能力期望很低)。 不设置 Statement.RETURN_GENERATED_KEYS 选项的一个有趣的副作用是错误消息,即完全模糊的“必须先执行语句才能获得任何结果。” 如果数据库返回生成的密钥,generatedKeys.next() 将返回 true。看,这是一个ResultSetclose() 只是为了释放资源。否则,您的数据库将长期用完它们,您的应用程序将中断。你只需要自己编写一些实用方法来完成关闭任务。另请参阅 this 和 this 答案。 大多数数据库/驱动程序的正确答案。但是,对于 Oracle,这不起作用。对于 Oracle,更改为:connection.prepareStatement(sql,new String[] "PK column name");【参考方案2】:

我正在从基于 JDBC 的单线程应用程序中访问 Microsoft SQL Server 2008 R2,并在不使用 RETURN_GENERATED_KEYS 属性或任何 PreparedStatement 的情况下拉回最后一个 ID。看起来像这样:

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“last ID”选项: http://msjawahar.wordpress.com/2008/01/25/how-to-find-the-last-identity-value-inserted-in-the-sql-server/ - 还不需要另外两个。

【讨论】:

应用程序只有一个线程并不会使竞争条件成为不可能:如果两个客户端插入一行并使用您的方法检索 ID,它可能会失败。 你为什么要这样做?我很高兴我不是在允许多线程时必须调试代码的可怜草皮!【参考方案3】:

我正在使用 SQLServer 2008,但我有一个开发限制:我不能为其使用新的驱动程序,我必须使用“com.microsoft.jdbc.sqlserver.SQLServerDriver”(我不能使用“com.microsoft.sqlserver.jdbc.SQLServerDriver”)。

这就是解决方案conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS) 为我抛出 java.lang.AbstractMethodError 的原因。 在这种情况下,我找到的一个可能的解决方案是微软建议的旧解决方案: 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)
        
        
    

这个解决方案对我有用!

我希望这会有所帮助!

【讨论】:

【参考方案4】:

如果在使用 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 是自动生成的 id。

【讨论】:

你的意思是BATCHID【参考方案5】:

    创建生成的列

    String generatedColumns[] =  "ID" ;
    

    将此生成的列传递给您的语句

    PreparedStatement stmtInsert = conn.prepareStatement(insertSQL, generatedColumns);
    

    使用ResultSet 对象获取语句上的 GeneratedKeys

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

【讨论】:

【参考方案6】:
Connection cn = DriverManager.getConnection("Host","user","pass");
Statement st = cn.createStatement("Ur Requet Sql");
int ret  = st.execute();

【讨论】:

对不起,这应该是什么? 1. Connection 中的 createStatement 方法不需要任何参数。 2.来自Statementexecute 方法需要一个带有查询的字符串。 3.execute方法返回:true如果第一个结果是ResultSet对象; false 如果是更新计数或没有结果。 docs.oracle.com/javase/7/docs/api/java/sql/…【参考方案7】:

我只想回复帖子,而不是comment。


接口java.sql.PreparedStatement

    columnIndexes « 您可以使用接受columnIndexes 和SQL 语句的prepareStatement 函数。 其中 columnIndexes 允许的常量标志是 Statement.RETURN_GENERATED_KEYS1 或 Statement.NO_GENERATED_KEYS[2],SQL 语句可能包含一个或多个“?” IN 参数占位符。

    语法 «

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

    例子:

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

    列名 « 列出列名,如'id', 'uniqueID', ...。在包含应返回的自动生成键的目标表中。如果 SQL 语句不是 INSERT 语句,驱动程序将忽略它们。

    语法 «

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

【讨论】:

在多线程运行时环境中使用此解决方案是否安全?【参考方案8】:

使用 Hibernate 的 NativeQuery,需要返回一个 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)抛出语法错误。之后,您可以从列表中获取结果 id(通常只包含一项)。

【讨论】:

【参考方案9】:

它也可以与普通的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.");
  

【讨论】:

对我有帮助。【参考方案10】:

就我而言->

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

【讨论】:

我仍然认为获得它的方法很长。我认为还会有更多的压缩解决方案。【参考方案11】:

如果你使用的是 Spring JDBC,你可以使用 Spring 的 GeneratedKeyHolder 类来获取插入的 ID。

看到这个答案... How to get inserted id using Spring Jdbctemplate.update(String sql, obj...args)

【讨论】:

【参考方案12】:

您可以使用以下 java 代码来获取新插入的 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);

【讨论】:

以上是关于如何在 JDBC 中获取插入 ID?的主要内容,如果未能解决你的问题,请参考以下文章

如何从 Oracle 中的 JDBC 批量插入中获取生成的密钥?

JDBC如何在自增字段中插入记录

获取 Google Apps 脚本中最后插入行的标识

使用 JDBC 获取 Oracle 11g 的最后插入 ID

如何使用 SimpleJdbcInsert 和 executeBatch 和 MYSQL JDBC 驱动程序获取生成的密钥?

如果插入语句给出重复键异常(在表中找到行 id=1)如何更新 JDBC(Postgresql)中的语句