在 MSSQL 中,採用 IDENTITY(1,1) 定義的自增主鍵列,插入新記錄後,應用層往往需要立即獲取該記錄的主鍵值(如用於關聯插入子表、返回前端展示、日誌追蹤)。傳統做法查詢 @@IDENTITY 或 SCOPE_IDENTITY() 存在併發風險與連接狀態依賴,本文對比多種方案,推薦 JDBC 3.0 標準 getGeneratedKeys() 為首選。
| 方案 | 原理 | 優點 | 缺點/風險 |
|---|---|---|---|
| 存儲過程 + OUTPUT 參數 | SP 內部 SELECT @OutID = SCOPE_IDENTITY(),JDBC CallableStatement.registerOutParameter |
邏輯封裝在資料庫層,可控制事務 | 增加資料庫對象維護成本;需額外網路往返;舊版驅動兼容性差 |
同一連接執行 SELECT @@IDENTITY |
插入後立即查詢全域變量 | 簡單直觀 | 嚴重併發 Bug:觸發器插入其他表會覆蓋值;連接池復用導致值錯亂 |
同一連接執行 SELECT SCOPE_IDENTITY() |
查詢當前作用域內最後身份值 | 解決觸發器干擾 | 仍依賴同一物理連接;連接池環境下極易出錯;需手動管理事務邊界 |
JDBC 3.0 getGeneratedKeys() |
驅動層協議級返回自增值,無額外查詢 | 標準化、無併發風險、效能最佳、跨資料庫可移植 | 需驅動支援(MSSQL JDBC 3.0+ 完全支援);批量插入需特殊處理 |
String sql = "INSERT INTO student (username) VALUES (?)";
try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
pstmt.setString(1, "張三");
int affected = pstmt.executeUpdate();
if (affected > 0) {
try (ResultSet rs = pstmt.getGeneratedKeys()) {
if (rs.next()) {
int generatedId = rs.getInt(1); // 或 getLong(1)
System.out.println("新記錄 ID:" + generatedId);
}
}
}
}
String sql = "INSERT INTO student (username) VALUES (?)";
try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
List<String> names = Arrays.asList("李四", "王五", "趙六");
for (String name : names) {
pstmt.setString(1, name);
pstmt.addBatch();
}
int[] results = pstmt.executeBatch();
try (ResultSet rs = pstmt.getGeneratedKeys()) {
int idx = 0;
while (rs.next() && idx < results.length) {
if (results[idx] == Statement.SUCCESS_NO_INFO || results[idx] > 0) {
System.out.println(names.get(idx) + " -> ID: " + rs.getLong(1));
}
idx++;
}
}
}
mssql-jdbc 9.4+ (JDBC 4.2/4.3),Maven 坐標:com.microsoft.sqlserver:mssql-jdbc。sendStringParametersAsUnicode=false 提升效能,trustServerCertificate=true 開發環境跳過 SSL 驗證。IDENTITY(1,1) 且為主鍵或唯一索引列。若受限於歷史架構必須使用存儲過程,正確寫法如下:
CREATE PROCEDURE usp_InsertStudent
@Username NVARCHAR(50),
@NewID INT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO student (username) VALUES (@Username);
SET @NewID = SCOPE_IDENTITY(); -- 必須用 SCOPE_IDENTITY()
END
<%
String url = "jdbc:sqlserver://localhost:1433;databaseName=school;sendStringParametersAsUnicode=false";
String user = "sa"; String pwd = "your_password";
try (Connection conn = DriverManager.getConnection(url, user, pwd);
CallableStatement cstmt = conn.prepareCall("{call usp_InsertStudent(?, ?)}")) {
cstmt.setString(1, "孫七");
cstmt.registerOutParameter(2, java.sql.Types.INTEGER);
cstmt.execute();
int newId = cstmt.getInt(2);
out.println("插入成功,自增 ID:" + newId);
} catch (SQLException e) {
e.printStackTrace();
out.println("錯誤:" + e.getMessage());
}
%>
Statement.NO_GENERATED_KEYS 或預設值:必須顯式傳入 Statement.RETURN_GENERATED_KEYS。ResultSet 後未調用 next():游標初始在第一行之前,必須先 next() 再 getInt()。conn.setAutoCommit(false),手動控制事務。getGeneratedKeys 支援不完整,請遷移至官方驅動。@@IDENTITY 會返回觸發器產生的值,絕對不要使用@@IDENTITY。在 JSP/Java 應用中獲取 MSSQL 自增主鍵,**首選 JDBC 3.0 標準 PreparedStatement.getGeneratedKeys()**,無需存儲過程、無併發風險、效能最高、代碼可移植。僅在極舊環境(驅動不支援、邏輯必須在資料庫層)下考慮存儲過程方案,且必須使用 SCOPE_IDENTITY() 而非 @@IDENTITY。配合連接池、事務管理、異常處理,構建健壯的資料存取層。