| 方案 | 核心 API | 適用場景 | 併發安全 | 性能 | 推薦指數 |
|---|---|---|---|---|---|
| Statement.RETURN_GENERATED_KEYS | PreparedStatement.getGeneratedKeys() | 單條/批量插入,標準 JDBC | ✅ 安全 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| SELECT LAST_INSERT_ID() | Session 級函數 | 單條插入,需同一連接 | ✅ 安全 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| SELECT MAX(id) | 聚合函數 | 極少併發、舊系統兼容 | ❌ 不安全 | ⭐⭐ | ⭐ |
String sql = "INSERT INTO users (username, password, email) VALUES (?, ?, ?)";
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
ps.setString(1, "zhangsan");
ps.setString(2, "encrypted_pwd");
ps.setString(3, "zhangsan@example.com");
int affected = ps.executeUpdate();
if (affected > 0) {
try (ResultSet rs = ps.getGeneratedKeys()) {
if (rs.next()) {
long generatedId = rs.getLong(1); // 獲取自增 ID
System.out.println("生成的 ID: " + generatedId);
}
}
}
}
Statement.RETURN_GENERATED_KEYS:告知驅動程序返回自增鍵getGeneratedKeys():返回包含自增值的 ResultSetrs.next() 循環遍歷
String sql = "INSERT INTO logs (level, message) VALUES (?, ?)";
try (PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
for (LogEntry entry : batch) {
ps.setString(1, entry.getLevel());
ps.setString(2, entry.getMessage());
ps.addBatch();
}
ps.executeBatch();
try (ResultSet rs = ps.getGeneratedKeys()) {
List ids = new ArrayList<>();
while (rs.next()) {
ids.add(rs.getLong(1));
}
// ids 順序與批次順序一一對應
}
}
// 方式 1:單獨查詢
String sql = "INSERT INTO users (name) VALUES (?)";
ps.executeUpdate(sql, new String[]{"test"});
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT LAST_INSERT_ID()")) {
if (rs.next()) {
long id = rs.getLong(1);
}
}
// 方式 2:MySQL 變量
long id = 0;
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT @@IDENTITY")) {
if (rs.next()) id = rs.getLong(1);
}
// 併發場景下的競態條件
// 線程 A 插入 → 線程 B 插入 → 線程 A 查 MAX(id) → 拿到線程 B 的 ID
@Insert("INSERT INTO users (username, email) VALUES (#{username}, #{email})")
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
int insertUser(User user);
// user 对象的 id 字段會自動回填
INSERT INTO users (username, email) VALUES (#{username}, #{email})
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // MySQL 自增
private Long id;
// 保存後 id 自動回填
}
@Repository
public interface UserRepository extends JpaRepository {
// save() 返回已持久化實體,包含生成的 ID
User save(User user);
}
autoCommit=true 或在同一事務中完成插入+獲取 ID
// ❌ 錯誤:不同事務,獲取不到 ID
@Transactional
public void insertWithNewTx() {
userMapper.insert(user); // 事務1提交
// 此處新事務,LAST_INSERT_ID() 已重置
Long id = jdbcTemplate.queryForObject("SELECT LAST_INSERT_ID()", Long.class);
}
// ✅ 正確:同一事務
@Transactional
public void insertInSameTx() {
userMapper.insert(user);
Long id = jdbcTemplate.queryForObject("SELECT LAST_INSERT_ID()", Long.class);
}
PreparedStatement + Statement.RETURN_GENERATED_KEYS —— 標準、安全、高效、支持批量LAST_INSERT_ID() —— 兼容舊驅動、存儲過程場景MAX(id) —— 併發不安全,除非極度確定單線程自增 ID 獲取看似微不足道,實則關係數據一致性核心。堅持標準 API,拒絕「聰明」的繞路方案,是資深工程師的基本素養。