最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java封裝Redis非阻塞分布式鎖徹底解決表單重復(fù)提交主鍵沖突問題

 更新時(shí)間:2026年03月23日 15:27:02   作者:墨著染霜華  
本文基于原生RedisTemplate封裝通用非阻塞分布式鎖工具類,實(shí)現(xiàn)鎖邏輯與業(yè)務(wù)邏輯解耦,兼顧易用性與安全性,同時(shí)解決鎖與事務(wù)配合、鎖釋放時(shí)機(jī)、失敗重試等核心坑點(diǎn),感興趣的朋友跟隨小編一起看看吧

?? 前言

在Web表單提交、接口調(diào)用場景中,經(jīng)常遇到用戶重復(fù)點(diǎn)擊、接口重復(fù)調(diào)用導(dǎo)致的問題:同一業(yè)務(wù)單號并發(fā)執(zhí)行插入操作,事務(wù)未提交前查詢無數(shù)據(jù),最終觸發(fā)數(shù)據(jù)庫主鍵/唯一鍵沖突報(bào)錯(cuò)。

針對這類高并發(fā)重復(fù)提交問題,本文基于原生RedisTemplate封裝通用非阻塞分布式鎖工具類,實(shí)現(xiàn)鎖邏輯與業(yè)務(wù)邏輯解耦,兼顧易用性與安全性,同時(shí)解決鎖與事務(wù)配合、鎖釋放時(shí)機(jī)、失敗重試等核心坑點(diǎn)。

?? 業(yè)務(wù)痛點(diǎn)回顧

  • 場景:表單提交接口,根據(jù)業(yè)務(wù)單號執(zhí)行插入操作
  • 問題:第一次請求未執(zhí)行完、事務(wù)未提交,第二次請求進(jìn)來查詢數(shù)據(jù)庫無數(shù)據(jù),也執(zhí)行插入,導(dǎo)致主鍵沖突
  • 需求:非阻塞獲取鎖(獲取失敗直接提示操作中)、執(zhí)行成功禁止重復(fù)提交、執(zhí)行失敗釋放鎖允許重試、60秒兜底防死鎖

??? 前提準(zhǔn)備

基于已有的RedisService工具類(包含setIfAbsent、Lua腳本釋放鎖),SpringBoot環(huán)境,JDK8+(函數(shù)式接口封裝)

1. 原有RedisService核心鎖方法(精簡版)

@Slf4j
@Component
@SuppressWarnings(value = {"unchecked", "rawtypes"})
public class RedisService {
    @Autowired
    public RedisTemplate redisTemplate;
    /**
     * 非阻塞獲取分布式鎖(原子操作)
     * @param lockKey 鎖鍵
     * @param requestId 唯一標(biāo)識,防止誤刪鎖
     * @param expireTime 過期時(shí)間(秒)
     * @return 獲取結(jié)果
     */
    public boolean tryLock(String lockKey, String requestId, long expireTime) {
        try {
            Boolean result = redisTemplate.opsForValue().setIfAbsent(
                    lockKey,
                    requestId,
                    expireTime,
                    TimeUnit.SECONDS
            );
            return Boolean.TRUE.equals(result);
        } catch (Exception e) {
            log.error("獲取分布式鎖失敗, lockKey:{}", lockKey, e);
            return false;
        }
    }
    /**
     * 安全釋放鎖(Lua腳本保證原子性)
     * @param lockKey 鎖鍵
     * @param requestId 唯一標(biāo)識
     * @return 釋放結(jié)果
     */
    public boolean releaseLock(String lockKey, String requestId) {
        String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        try {
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            redisScript.setScriptText(luaScript);
            redisScript.setResultType(Long.class);
            Long result = (Long) redisTemplate.execute(
                    redisScript,
                    Collections.singletonList(lockKey),
                    requestId
            );
            return result != null && result > 0;
        } catch (Exception e) {
            log.error("釋放分布式鎖失敗, lockKey:{}", lockKey, e);
            return false;
        }
    }
    // 省略其他緩存方法...
}

?? 核心封裝:通用分布式鎖工具類

利用Supplier函數(shù)式接口,將鎖的獲取、釋放、異常處理封裝成公共方法,業(yè)務(wù)代碼無需重復(fù)編寫鎖邏輯,只需關(guān)注核心業(yè)務(wù)。

RedisLockUtil 封裝類

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.UUID;
import java.util.function.Supplier;
/**
 * 通用Redis非阻塞分布式鎖工具類
 * 適配:表單防重、接口防重復(fù)提交、并發(fā)插入控制
 */
@Slf4j
@Component
public class RedisLockUtil {
    @Autowired
    private RedisService redisService;
    /**
     * 非阻塞鎖執(zhí)行通用方法
     * @param lockKey 鎖唯一鍵(建議:lock:業(yè)務(wù)模塊:業(yè)務(wù)單號)
     * @param expireSeconds 鎖過期時(shí)間(秒,兜底防死鎖)
     * @param businessLogic 業(yè)務(wù)邏輯代碼塊
     * @param <T> 返回值泛型
     * @return 業(yè)務(wù)執(zhí)行結(jié)果
     */
    public <T> T executeWithLock(String lockKey, int expireSeconds, Supplier<T> businessLogic) {
        // 生成唯一請求ID,防止誤刪其他線程的鎖
        String requestId = UUID.randomUUID().toString();
        // 1. 非阻塞獲取鎖
        boolean isLocked = redisService.tryLock(lockKey, requestId, expireSeconds);
        if (!isLocked) {
            log.warn("[分布式鎖] 獲取失敗,鎖鍵:{},操作進(jìn)行中", lockKey);
            throw new RuntimeException("操作進(jìn)行中,請勿重復(fù)提交!");
        }
        try {
            // 2. 執(zhí)行核心業(yè)務(wù)邏輯
            return businessLogic.get();
        } catch (Exception e) {
            log.error("[分布式鎖] 業(yè)務(wù)執(zhí)行失敗,鎖鍵:{},允許重試", lockKey, e);
            throw new RuntimeException("提交失?。? + e.getMessage() + ",可重試!");
        } finally {
            // 3. 無論成功/失敗,必須釋放鎖
            boolean isReleased = redisService.releaseLock(lockKey, requestId);
            if (!isReleased) {
                log.warn("[分布式鎖] 釋放失敗,鎖鍵:{},requestId:{}(可能已過期自動(dòng)釋放)", lockKey, requestId);
            }
        }
    }
}

?? 實(shí)戰(zhàn)調(diào)用:表單提交防重

調(diào)用封裝好的工具類,代碼極度簡潔,鎖邏輯完全剝離,業(yè)務(wù)代碼更清晰。

業(yè)務(wù)層調(diào)用代碼

@Service
@Slf4j
public class FormSubmitService {
    @Autowired
    private RedisLockUtil redisLockUtil;
    // 業(yè)務(wù)Mapper,隱藏具體表信息
    @Autowired
    private BizFormMapper bizFormMapper;
    /**
     * 表單提交核心方法
     * @param djbh 業(yè)務(wù)單號(唯一標(biāo)識)
     * @param formDTO 表單參數(shù)
     * @return 接口返回結(jié)果
     */
    public Result submitForm(String djbh, BizFormDTO formDTO) {
        try {
            // 構(gòu)造鎖鍵:隱藏具體業(yè)務(wù)模塊,規(guī)范命名
            String lockKey = "lock:biz_form_submit:" + djbh;
            // 調(diào)用鎖工具類,60秒過期兜底,執(zhí)行業(yè)務(wù)邏輯
            return redisLockUtil.executeWithLock(lockKey, 60, () -> {
                // ========== 核心業(yè)務(wù)邏輯 ==========
                // 1. 查詢是否已存在(成功后禁止重復(fù)提交)
                Object existData = bizFormMapper.selectByDjbh(djbh);
                if (existData != null) {
                    return Result.fail("該單據(jù)已提交,請勿重復(fù)操作!");
                }
                // 2. 執(zhí)行插入操作
                bizFormMapper.insertForm(formDTO);
                return Result.success("提交成功!");
                // ================================
            });
        } catch (RuntimeException e) {
            // 捕獲鎖異常、業(yè)務(wù)異常,統(tǒng)一返回
            return Result.fail(e.getMessage());
        }
    }
    // 省略Result成功/失敗封裝方法...
}

?? 關(guān)鍵避坑點(diǎn)(必看)

1. 鎖與事務(wù)的配合問題

錯(cuò)誤做法:鎖寫在@Transactional事務(wù)內(nèi)部,導(dǎo)致鎖釋放早于事務(wù)提交,仍有并發(fā)沖突風(fēng)險(xiǎn)。

正確做法:鎖包裹事務(wù),本文封裝方式天然滿足(鎖在事務(wù)外層,事務(wù)提交后再釋放鎖)。

2. 鎖釋放時(shí)機(jī)

  • 執(zhí)行成功:finally塊主動(dòng)釋放鎖,后續(xù)請求進(jìn)來會查詢數(shù)據(jù)庫已存在,直接拒絕,無風(fēng)險(xiǎn)
  • 執(zhí)行失敗:釋放鎖,允許用戶重試提交
  • 服務(wù)宕機(jī):Redis 60秒自動(dòng)過期,避免死鎖

3. 鎖鍵設(shè)計(jì)規(guī)范

遵循 lock:業(yè)務(wù)模塊:唯一單號 格式,保證鎖的細(xì)粒度,避免全局鎖影響性能。

4. 非阻塞特性

獲取鎖失敗直接返回提示,不阻塞線程,提升用戶體驗(yàn)和接口性能,適配表單提交場景。

?? 方案優(yōu)勢

  • 解耦徹底:鎖邏輯與業(yè)務(wù)代碼分離,一處封裝,多處復(fù)用
  • 安全可靠:requestId防誤刪、Lua腳本釋放、過期兜底,杜絕死鎖
  • 易用簡潔:調(diào)用時(shí)只需傳入鎖鍵、過期時(shí)間、業(yè)務(wù)代碼塊,零冗余
  • 適配場景:表單防重、接口限流、并發(fā)插入控制、冪等性保證

?? 總結(jié)

針對Java后端表單重復(fù)提交、并發(fā)主鍵沖突問題,通過Redis非阻塞分布式鎖+函數(shù)式封裝,既能從根源解決并發(fā)問題,又能保證代碼優(yōu)雅性。核心思路是鎖控制并發(fā),數(shù)據(jù)庫狀態(tài)控制冪等,雙層保障杜絕重復(fù)提交。

本文封裝的工具類可直接接入項(xiàng)目,適配絕大多數(shù)防重復(fù)提交場景,無需重復(fù)造輪子。

到此這篇關(guān)于Java封裝Redis非阻塞分布式鎖徹底解決表單重復(fù)提交主鍵沖突問題的文章就介紹到這了,更多相關(guān)java redis非阻塞分布式鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • IntelliJ IDEA中Scala、sbt、maven配置教程

    IntelliJ IDEA中Scala、sbt、maven配置教程

    這篇文章主要介紹了IntelliJ IDEA中Scala、sbt、maven配置教程,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • Java NoClassDefFoundError運(yùn)行時(shí)錯(cuò)誤分析解決

    Java NoClassDefFoundError運(yùn)行時(shí)錯(cuò)誤分析解決

    在Java開發(fā)中,NoClassDefFoundError是一種常見的運(yùn)行時(shí)錯(cuò)誤,它通常表明Java虛擬機(jī)在嘗試加載一個(gè)類時(shí)未能找到該類的定義,這個(gè)問題經(jīng)常與類路徑配置不正確或者缺失的庫文件有關(guān),需要的朋友可以參考下
    2025-05-05
  • jstack+jdb命令查看線程及死鎖堆棧信息的實(shí)例

    jstack+jdb命令查看線程及死鎖堆棧信息的實(shí)例

    這篇文章主要介紹了jstack+jdb命令查看線程及死鎖堆棧信息的實(shí)例,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-02-02
  • 解讀@RequestBody與post請求的關(guān)系

    解讀@RequestBody與post請求的關(guān)系

    這篇文章主要介紹了解讀@RequestBody與post請求的關(guān)系,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-12-12
  • Java實(shí)現(xiàn)輕松提取word和pdf文檔內(nèi)容

    Java實(shí)現(xiàn)輕松提取word和pdf文檔內(nèi)容

    這篇文章主要為大家詳細(xì)介紹了如何使用Java實(shí)現(xiàn)輕松提取word和pdf文檔內(nèi)容,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2025-10-10
  • SpringBoot3整合Mybatis完整版實(shí)例

    SpringBoot3整合Mybatis完整版實(shí)例

    本文詳細(xì)介紹了SpringBoot3整合MyBatis的完整步驟,包括添加數(shù)據(jù)庫驅(qū)動(dòng)和MyBatis依賴、配置數(shù)據(jù)源和MyBatis、創(chuàng)建表和Bean類、編寫Mapper接口和XML文件、創(chuàng)建Controller類以及配置掃描包,通過這些步驟,可以實(shí)現(xiàn)SpringBoot3與MyBatis的成功整合,并進(jìn)行功能測試
    2025-01-01
  • Java五子棋簡單實(shí)現(xiàn)代碼舉例

    Java五子棋簡單實(shí)現(xiàn)代碼舉例

    Java五子棋游戲是一種經(jīng)典的兩人對戰(zhàn)棋類游戲,它基于簡單的規(guī)則,即任何一方的棋子在棋盤上形成連續(xù)的五個(gè),無論是橫、豎還是斜線,都將獲勝,這篇文章主要介紹了Java五子棋實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下
    2024-10-10
  • SpringBoot Actuator未授權(quán)訪問漏洞的排查和解決方法

    SpringBoot Actuator未授權(quán)訪問漏洞的排查和解決方法

    Spring Boot Actuator 是開發(fā)和管理生產(chǎn)級 Spring Boot 應(yīng)用程序的重要工具,它可以幫助你確保應(yīng)用程序的穩(wěn)定性和性能,本文給大家介紹了SpringBoot Actuator未授權(quán)訪問漏洞的排查和解決方法,需要的朋友可以參考下
    2024-05-05
  • 使用ElasticSearch6.0快速實(shí)現(xiàn)全文搜索功能的示例代碼

    使用ElasticSearch6.0快速實(shí)現(xiàn)全文搜索功能的示例代碼

    本篇文章主要介紹了使用ElasticSearch6.0快速實(shí)現(xiàn)全文搜索功能,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-02-02
  • 實(shí)例講解MyBatis如何防止SQL注入

    實(shí)例講解MyBatis如何防止SQL注入

    這篇文章通過實(shí)例代碼介紹MyBatis如何防止SQL注入,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-12-12

最新評論

临夏县| 黔江区| 怀宁县| 台南市| 遵化市| 容城县| 鄯善县| 繁峙县| 凤山市| 江川县| 夏津县| 巴彦县| 邢台市| 大理市| 英德市| 如皋市| 庄浪县| 平昌县| 曲沃县| 梅州市| 北辰区| 安阳市| 新营市| 宁都县| 沙湾县| 通许县| 腾冲县| 新乐市| 舟山市| 宾阳县| 太仓市| 黄石市| 南江县| 紫云| 通州区| 湖南省| 白银市| 拉萨市| 宝应县| 抚顺县| 铁岭县|