首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

MyBatis 插入主鍵衝突異常處理:預檢查 vs 異常捕獲兩種方案對比

     編輯:程式設計綜合問答

問題場景

MyBatis 執行 INSERT 時因主鍵重複(Duplicate entry 'xxx' for key 'PRIMARY')拋出 PersistenceException / MySQLIntegrityConstraintViolationException,導致業務流程中斷、事務回滾。

方案對比

方案核心邏輯優點缺點適用場景
方案一:異常捕獲轉換try-catch 捕獲異常,解析錯誤碼/訊息,返回友好提示代碼簡潔、無額外查詢、併發安全(資料庫鎖保證)依賴特定資料庫錯誤碼、異常處理有性能開銷低併發、主鍵衝突極少見、業務允許「嘗試插入失敗則提示」
方案二:預查詢判斷先 SELECT id FROM table WHERE id = ?,不存在再 INSERT邏輯直觀、可自定義衝突處理(更新/忽略/報錯)**併發不安全**(TOCTOU 競態條件)、雙倍查詢開銷單線程/低併發、需區分「新增」與「更新」邏輯
方案三:資料庫原生 UPSERT(推薦)INSERT ... ON DUPLICATE KEY UPDATE (MySQL) / MERGE (Oracle/SQL Server) / ON CONFLICT (PostgreSQL)**原子操作、併發安全、單次交互、效能最高**語法因資料庫異、需處理「更新哪些欄位」邏輯**所有生產環境首選**

方案三:MyBatis UPSERT 實戰

MySQL:ON DUPLICATE KEY UPDATE

<insert id="insertOrUpdateUser" parameterType="User">
    INSERT INTO t_user (id, username, email, create_time, update_time)
    VALUES (#{id}, #{username}, #{email}, NOW(), NOW())
    ON DUPLICATE KEY UPDATE
        username = VALUES(username),
        email = VALUES(email),
        update_time = NOW()
</insert>

PostgreSQL:ON CONFLICT DO UPDATE

<insert id="insertOrUpdateUser" parameterType="User">
    INSERT INTO t_user (id, username, email, create_time, update_time)
    VALUES (#{id}, #{username}, #{email}, NOW(), NOW())
    ON CONFLICT (id) DO UPDATE SET
        username = EXCLUDED.username,
        email = EXCLUDED.email,
        update_time = NOW()
</insert>

Oracle:MERGE INTO

<insert id="insertOrUpdateUser" parameterType="User">
    MERGE INTO t_user t
    USING (SELECT #{id} AS id FROM dual) s
    ON (t.id = s.id)
    WHEN MATCHED THEN UPDATE SET
        t.username = #{username},
        t.email = #{email},
        t.update_time = SYSDATE
    WHEN NOT MATCHED THEN INSERT (id, username, email, create_time, update_time)
    VALUES (#{id}, #{username}, #{email}, SYSDATE, SYSDATE)
</insert>

方案一:異常捕獲實現(若無法用 UPSERT)

@Service
public class UserService {
    @Autowired UserMapper userMapper;

    public Result<Void> insertUser(User user) {
        try {
            userMapper.insert(user);
            return Result.success();
        } catch (PersistenceException e) {
            Throwable cause = e.getCause();
            if (cause instanceof MySQLIntegrityConstraintViolationException) {
                String msg = cause.getMessage();
                if (msg != null && msg.contains("Duplicate entry")) {
                    return Result.error("主鍵已存在:" + user.getId());
                }
            }
            throw e; // 非主鍵衝突異常向上拋出
        }
    }
}

方案二:預查詢(僅教學參考,生產慎用)

public Result<Void> insertUserSafe(User user) {
    // 併發風險:此處與 INSERT 之間有時間窗口
    if (userMapper.selectById(user.getId()) != null) {
        return Result.error("主鍵已存在");
    }
    userMapper.insert(user);
    return Result.success();
}

最佳實踐總結

  1. 首選資料庫原生 UPSERT:原子性、高效能、併發安全,MyBatis XML 中直接寫對應語法
  2. 主鍵策略優化:使用雪花算法 ID、UUID、資料庫自增 ID,從源頭降低衝突概率
  3. 業務層面冪等設計:相同業務主鍵(如訂單號)重複請求直接返回成功,避免觸發資料庫衝突
  4. 異常捕獲作為最後防線:捕獲無法預期的約束違反,記錄日誌、告警、返回友好提示

採用 UPSERT 重構後,主鍵衝突不再拋出異常,而是自動轉為更新或忽略,徹底解決插入異常導致業務中斷問題。

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