首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 獲取 MySQL 自增主鍵 ID 的三種方案深度對比與最佳實踐

     編輯:Java 相關知識

方案對比總覽

方案核心 API適用場景併發安全性能推薦指數
Statement.RETURN_GENERATED_KEYSPreparedStatement.getGeneratedKeys()單條/批量插入,標準 JDBC✅ 安全⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
SELECT LAST_INSERT_ID()Session 級函數單條插入,需同一連接✅ 安全⭐⭐⭐⭐⭐⭐⭐⭐
SELECT MAX(id)聚合函數極少併發、舊系統兼容❌ 不安全⭐⭐⭐

方案一:JDBC 標準 API(首選推薦)

核心代碼


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():返回包含自增值的 ResultSet
  • 支持批量插入獲取多個 ID:rs.next() 循環遍歷
  • MySQL Connector/J 5.1.35+ 完美支持

批量插入獲取多 ID 示例


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 順序與批次順序一一對應
    }
}

方案二:LAST_INSERT_ID() / @@IDENTITY

適用場景

  • 舊版驅動不支持 getGeneratedKeys
  • 存儲過程/觸發器中獲取
  • 需要在同一事務中多次引用新 ID

實現代碼


// 方式 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);
}

關鍵特性

  • Session 級隔離:連接 A 的插入不影響連接 B 的 LAST_INSERT_ID()
  • 語句級更新:只有成功的 INSERT/UPDATE 才更新該值
  • 多行插入:返回第一行的自增 ID
  • 事務回滾:回滾不恢復 LAST_INSERT_ID() 值

方案三:SELECT MAX(id)(強烈不推薦用於併發環境)

致命缺陷


// 併發場景下的競態條件
// 線程 A 插入 → 線程 B 插入 → 線程 A 查 MAX(id) → 拿到線程 B 的 ID
  • 無法區分當前會話插入的記錄
  • 高併發下極大概率拿錯 ID
  • 僅適用於單線程、初始化數據等極少數場景

MyBatis / JPA / Spring Data 實戰整合

MyBatis 注解方式


@Insert("INSERT INTO users (username, email) VALUES (#{username}, #{email})")
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
int insertUser(User user);

// user 对象的 id 字段會自動回填

MyBatis XML 方式



    INSERT INTO users (username, email) VALUES (#{username}, #{email})

JPA / Hibernate


@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // MySQL 自增
    private Long id;
    
    // 保存後 id 自動回填
}

Spring Data JPA


@Repository
public interface UserRepository extends JpaRepository {
    // save() 返回已持久化實體,包含生成的 ID
    User save(User user);
}

連接池與事務邊界注意事項

連接池配置

  • 確保 autoCommit=true 或在同一事務中完成插入+獲取 ID
  • HikariCP / Druid 默認自動提交,滿足需求

事務傳播陷阱


// ❌ 錯誤:不同事務,獲取不到 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);
}

總結建議

  1. 首選:PreparedStatement + Statement.RETURN_GENERATED_KEYS —— 標準、安全、高效、支持批量
  2. 備選:LAST_INSERT_ID() —— 兼容舊驅動、存儲過程場景
  3. 禁用:MAX(id) —— 併發不安全,除非極度確定單線程
  4. 框架層:優先使用 MyBatis @Options、JPA @GeneratedValue 等聲明式配置

自增 ID 獲取看似微不足道,實則關係數據一致性核心。堅持標準 API,拒絕「聰明」的繞路方案,是資深工程師的基本素養。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved