- 通過JDBC2.0提供的insertRow()方式
- 通過JDBC3.0提供的getGeneratedKeys()方式
- 通過SQL select LAST_INSERT_ID()函數
- 通過SQL @@IDENTITY 變量
1. 通過JDBC2.0提供的insertRow()方式
自jdbc2.0以來,可以通過下面的方式執行。
- Statement stmt = null;
- ResultSet rs = null;
- try {
- stmt = conn.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, // 創建Statement
- java.sql.ResultSet.CONCUR_UPDATABLE);
- stmt.executeUpdate("DROP TABLE IF EXISTS autoIncTutorial");
- stmt.executeUpdate( // 創建demo表
- "CREATE TABLE autoIncTutorial ("
- + "priKey INT NOT NULL AUTO_INCREMENT, "
- + "dataField VARCHAR(64), PRIMARY KEY (priKey))");
- rs = stmt.executeQuery("SELECT priKey, dataField " // 檢索數據
- + "FROM autoIncTutorial");
- rs.moveToInsertRow(); // 移動游標到待插入行(未創建的偽記錄)
- rs.updateString("dataField", "AUTO INCREMENT here?"); // 修改內容
- rs.insertRow(); // 插入記錄
- rs.last(); // 移動游標到最后一行
- int autoIncKeyFromRS = rs.getInt("priKey"); // 獲取剛插入記錄的主鍵preKey
- rs.close();
- rs = null;
- System.out.println("Key returned for inserted row: "
- + autoIncKeyFromRS);
- } finally {
- // rs,stmt的close()清理
- }
Statement stmt = null; ResultSet rs = null; try { stmt = conn.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, // 創建Statement java.sql.ResultSet.CONCUR_UPDATABLE); stmt.executeUpdate("DROP TABLE IF EXISTS autoIncTutorial"); stmt.executeUpdate( // 創建demo表 "CREATE TABLE autoIncTutorial (" + "priKey INT NOT NULL AUTO_INCREMENT, " + "dataField VARCHAR(64), PRIMARY KEY (priKey))"); rs = stmt.executeQuery("SELECT priKey, dataField " // 檢索數據 + "FROM autoIncTutorial"); rs.moveToInsertRow(); // 移動游標到待插入行(未創建的偽記錄) rs.updateString("dataField", "AUTO INCREMENT here?"); // 修改內容 rs.insertRow(); // 插入記錄 rs.last(); // 移動游標到最后一行 int autoIncKeyFromRS = rs.getInt("priKey"); // 獲取剛插入記錄的主鍵preKey rs.close(); rs = null; System.out.println("Key returned for inserted row: " + autoIncKeyFromRS); } finally { // rs,stmt的close()清理 }
優點:早期較為通用的做法
缺點:需要操作ResultSet的游標,代碼冗長。
2. 通過JDBC3.0提供的getGeneratedKeys()方式
- Statement stmt = null;
- ResultSet rs = null;
- try {
- stmt = conn.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY,
- java.sql.ResultSet.CONCUR_UPDATABLE);
- // ...
- // 省略若干行(如上例般創建demo表)
- // ...
- stmt.executeUpdate(
- "INSERT INTO autoIncTutorial (dataField) "
- + "values ('Can I Get the Auto Increment Field?')",
- Statement.RETURN_GENERATED_KEYS); // 向驅動指明需要自動獲取generatedKeys!
- int autoIncKeyFromApi = -1;
- rs = stmt.getGeneratedKeys(); // 獲取自增主鍵!
- if (rs.next()) {
- autoIncKeyFromApi = rs.getInt(1);
- } else {
- // throw an exception from here
- }
- rs.close();
- rs = null;
- System.out.println("Key returned from getGeneratedKeys():"
- + autoIncKeyFromApi);
- } finally { ... }
Statement stmt = null; ResultSet rs = null; try { stmt = conn.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_UPDATABLE); // ... // 省略若干行(如上例般創建demo表) // ... stmt.executeUpdate( "INSERT INTO autoIncTutorial (dataField) " + "values ('Can I Get the Auto Increment Field?')", Statement.RETURN_GENERATED_KEYS); // 向驅動指明需要自動獲取generatedKeys! int autoIncKeyFromApi = -1; rs = stmt.getGeneratedKeys(); // 獲取自增主鍵! if (rs.next()) { autoIncKeyFromApi = rs.getInt(1); } else { // throw an exception from here } rs.close(); rs = null; System.out.println("Key returned from getGeneratedKeys():" + autoIncKeyFromApi); } finally { ... }這種方式只需要2個步驟:1. 在executeUpdate時激活自動獲取key; 2.調用Statement的getGeneratedKeys()接口 優點:
1. 操作方便,代碼簡潔
2. jdbc3.0的標准
3. 效率高,因為沒有額外訪問數據庫
這里補充下,
a.在jdbc3.0之前,每個jdbc driver的實現都有自己獲取自增主鍵的接口。在mysql jdbc2.0的driver
org.gjt.mm.mysql中,getGeneratedKeys()函數就實現在org.gjt.mm.mysql.jdbc2.Staement.getGeneratedKeys()中。這樣直接引用的話,移植性會有很大影響。JDBC3.0通過標准的getGeneratedKeys很好的彌補了這點。
b.關於getGeneratedKeys(),官網還有更詳細解釋:OracleJdbcGuide
3. 通過SQL select LAST_INSERT_ID()
- Statement stmt = null;
- ResultSet rs = null;
- try {
- stmt = conn.createStatement();
- // ...
- // 省略建表
- // ...
- stmt.executeUpdate(
- "INSERT INTO autoIncTutorial (dataField) "
- + "values ('Can I Get the Auto Increment Field?')");
- int autoIncKeyFromFunc = -1;
- rs = stmt.executeQuery("SELECT LAST_INSERT_ID()"); // 通過額外查詢獲取generatedKey
- if (rs.next()) {
- autoIncKeyFromFunc = rs.getInt(1);
- } else {
- // throw an exception from here
- }
- rs.close();
- System.out.println("Key returned from " +
- "'SELECT LAST_INSERT_ID()': " +
- autoIncKeyFromFunc);
- } finally {...}
Statement stmt = null; ResultSet rs = null; try { stmt = conn.createStatement(); // ... // 省略建表 // ... stmt.executeUpdate( "INSERT INTO autoIncTutorial (dataField) " + "values ('Can I Get the Auto Increment Field?')"); int autoIncKeyFromFunc = -1; rs = stmt.executeQuery("SELECT LAST_INSERT_ID()"); // 通過額外查詢獲取generatedKey if (rs.next()) { autoIncKeyFromFunc = rs.getInt(1); } else { // throw an exception from here } rs.close(); System.out.println("Key returned from " + "'SELECT LAST_INSERT_ID()': " + autoIncKeyFromFunc); } finally {...}這種方式沒什么好說的,就是額外查詢一次函數LAST_INSERT_ID().
優點:簡單方便
缺點:相對JDBC3.0的getGeneratedKeys(),需要額外多一次數據庫查詢。
補充:
1. 這個函數,在mysql5.5手冊的定義是:“returns a BIGINT (64-bit) value representing the first automatically generated value successfully inserted for an AUTO_INCREMENT column as a result of the most recently executed INSERT statement.”。文檔點此
2. 這個函數,在connection維度上是“線程安全的”。就是說,每個mysql連接會有個獨立保存LAST_INSERT_ID()的結果,並且只會被當前連接最近一次insert操作所更新。也就是2個連接同時執行insert語句時候,分別調用的LAST_INSERT_ID()不會相互覆蓋。舉個栗子:連接A插入表后LAST_INSERT_ID()返回100,連接B插入表后LAST_INSERT_ID()返回101,但是連接A重復執行LAST_INSERT_ID()的時候,始終返回100,而不是101。這個可以通過監控mysql連接數和執行結果來驗證,這里不詳述實驗過程。
3. 在上面那點的基礎上,如果在同一個連接的前提下同時執行insert,那可能2次操作的返回值會相互覆蓋。因為LAST_INSERT_ID()的隔離程度是連接級別的。這點,getGeneratedKeys()是可以做的更好,因為getGeneratedKeys()是statement級別的。同個connection的多次statement,getGeneratedKeys()是不會被相互覆蓋。
4. 通過SQL SELECT @@IDENTITY
這個方式和LAST_INSERT_ID()效果是一樣的。官網文檔如此表述:“This variable is a synonym for the last_insert_id variable. It exists for compatibility with other database systems. You can read its value with SELECT @@identity, and set it using SET identity.” 文檔點此
重要補充:
無論是SELECT LAST_INSERT_ID()還是SELECT @@IDENTITY,對於一條insert語句插入多條記錄,永遠只會返回第一條插入記錄的generatedKey.如:
- INSERT INTO t VALUES
- -> (NULL, 'Mary'), (NULL, 'Jane'), (NULL, 'Lisa');
INSERT INTO t VALUES -> (NULL, 'Mary'), (NULL, 'Jane'), (NULL, 'Lisa');LAST_INSERT_ID(), @@IDENTITY都只會返回'Mary'所在的那條記錄的generatedKey
小結
所以,最好還是通過JDBC3 提供的getGeneratedKeys()函數來獲取insert記錄的主鍵。不但簡單,而且效率高。
在mybatis中,就有相關設置:
- <insert id="save" parameterType="MappedObject" useGeneratedKeys="true" keyProperty="id">
- </insert>