java中接口冪等性的五種實(shí)現(xiàn)方法
在 Java 中保證接口冪等性(即多次調(diào)用同一接口產(chǎn)生與單次調(diào)用相同的結(jié)果,不會(huì)引發(fā)副作用),需要結(jié)合業(yè)務(wù)場(chǎng)景選擇合適的方案。以下是常見(jiàn)的實(shí)現(xiàn)方式及技術(shù)細(xì)節(jié):
1. 基于唯一標(biāo)識(shí)的去重機(jī)制
核心思想:為每次請(qǐng)求生成唯一標(biāo)識(shí)(如訂單號(hào)、請(qǐng)求 ID),服務(wù)端通過(guò)記錄該標(biāo)識(shí)是否已處理,避免重復(fù)執(zhí)行。
實(shí)現(xiàn)方式:
數(shù)據(jù)庫(kù)唯一約束:將唯一標(biāo)識(shí)作為數(shù)據(jù)庫(kù)表的唯一索引,重復(fù)請(qǐng)求會(huì)觸發(fā)主鍵沖突異常,直接返回成功(或錯(cuò)誤提示)。
// 示例:訂單表唯一索引(order_no)
@Entity
@Table(uniqueConstraints = {@UniqueConstraint(columnNames = "orderNo")})
public class Order {
@Id
private Long id;
private String orderNo; // 唯一訂單號(hào)(作為冪等標(biāo)識(shí))
// 其他字段...
}
// 服務(wù)層處理
@Transactional
public Result createOrder(OrderDTO dto) {
try {
// 嘗試插入訂單(依賴數(shù)據(jù)庫(kù)唯一約束)
Order order = new Order();
order.setOrderNo(dto.getOrderNo());
orderRepository.save(order);
// 執(zhí)行后續(xù)業(yè)務(wù)(如扣減庫(kù)存)
return Result.success();
} catch (DataIntegrityViolationException e) {
// 唯一約束沖突,說(shuō)明已處理過(guò)
log.warn("訂單已存在: {}", dto.getOrderNo());
return Result.success(); // 或返回已有結(jié)果
}
}
緩存記錄(Redis) :利用 Redis 的SETNX(不存在則設(shè)置)特性,判斷請(qǐng)求是否已處理。
@Autowired
private StringRedisTemplate redisTemplate;
public Result processRequest(String requestId) {
// 嘗試設(shè)置唯一標(biāo)識(shí),過(guò)期時(shí)間防止內(nèi)存溢出
Boolean isFirst = redisTemplate.opsForValue().setIfAbsent(
"idempotent:" + requestId,
"processed",
1, TimeUnit.HOURS
);
if (Boolean.TRUE.equals(isFirst)) {
// 首次請(qǐng)求,執(zhí)行業(yè)務(wù)邏輯
doBusiness();
return Result.success();
} else {
// 重復(fù)請(qǐng)求,返回已有結(jié)果
return Result.success("已處理");
}
}
2. 令牌(Token)機(jī)制
核心思想:客戶端先向服務(wù)端申請(qǐng)令牌,請(qǐng)求接口時(shí)攜帶令牌,服務(wù)端驗(yàn)證令牌有效性后處理業(yè)務(wù),并標(biāo)記令牌為已使用。
實(shí)現(xiàn)流程:
- 客戶端請(qǐng)求獲取令牌(服務(wù)端生成令牌并存儲(chǔ)到 Redis)。
- 客戶端攜帶令牌調(diào)用業(yè)務(wù)接口。
- 服務(wù)端校驗(yàn)令牌:存在則處理業(yè)務(wù)并刪除令牌;不存在則拒絕。
// 生成令牌
public String generateToken() {
String token = UUID.randomUUID().toString();
redisTemplate.opsForValue().set("token:" + token, "valid", 30, TimeUnit.MINUTES);
return token;
}
// 校驗(yàn)令牌并處理業(yè)務(wù)
public Result doBusiness(String token, BusinessDTO dto) {
// 刪除令牌(原子操作,確保唯一處理)
Boolean isValid = redisTemplate.delete("token:" + token);
if (Boolean.TRUE.equals(isValid)) {
// 令牌有效,執(zhí)行業(yè)務(wù)
process(dto);
return Result.success();
} else {
// 令牌無(wú)效(已使用或過(guò)期)
return Result.fail("重復(fù)請(qǐng)求");
}
}
3. 樂(lè)觀鎖機(jī)制
核心思想:適用于更新操作,通過(guò)版本號(hào)控制,確保只有版本匹配時(shí)才執(zhí)行更新,避免重復(fù)更新。
@Entity
public class Product {
@Id
private Long id;
private Integer stock; // 庫(kù)存
private Integer version; // 版本號(hào)
}
@Transactional
public Result reduceStock(Long productId, Integer quantity) {
// 查詢商品及當(dāng)前版本
Product product = productRepository.findById(productId)
.orElseThrow(() -> new RuntimeException("商品不存在"));
// 檢查庫(kù)存
if (product.getStock() < quantity) {
return Result.fail("庫(kù)存不足");
}
// 樂(lè)觀鎖更新(where條件包含版本號(hào))
int rows = productRepository.reduceStock(
productId,
quantity,
product.getVersion() // 當(dāng)前版本
);
if (rows > 0) {
return Result.success();
} else {
// 版本不匹配,說(shuō)明已被其他請(qǐng)求處理
return Result.fail("操作沖突,請(qǐng)重試");
}
}
// Repository層SQL(JPA示例)
@Modifying
@Query("UPDATE Product p SET p.stock = p.stock - :quantity, p.version = p.version + 1 " +
"WHERE p.id = :id AND p.version = :version")
int reduceStock(@Param("id") Long id,
@Param("quantity") Integer quantity,
@Param("version") Integer version);
4. 狀態(tài)機(jī)控制
核心思想:通過(guò)狀態(tài)流轉(zhuǎn)約束,確保接口只能在特定狀態(tài)下執(zhí)行,避免重復(fù)操作(如訂單狀態(tài)從 “待支付” 到 “已支付” 的單向流轉(zhuǎn))。
public enum OrderStatus {
CREATED(1, "待支付"),
PAID(2, "已支付"),
CANCELLED(3, "已取消");
private int code;
private String desc;
// 構(gòu)造器、getter...
}
@Transactional
public Result payOrder(Long orderId) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new RuntimeException("訂單不存在"));
// 僅允許“待支付”狀態(tài)執(zhí)行支付
if (order.getStatus() != OrderStatus.CREATED) {
log.warn("訂單狀態(tài)異常: {}", orderId);
return Result.success("訂單已處理"); // 重復(fù)支付請(qǐng)求直接返回成功
}
// 執(zhí)行支付邏輯(如調(diào)用支付網(wǎng)關(guān))
boolean paySuccess = paymentGateway.pay(order);
if (paySuccess) {
order.setStatus(OrderStatus.PAID);
orderRepository.save(order);
return Result.success();
} else {
return Result.fail("支付失敗");
}
}
5. 分布式鎖(高并發(fā)場(chǎng)景)
核心思想:在分布式系統(tǒng)中,通過(guò)分布式鎖(如 Redis、ZooKeeper)確保同一時(shí)間只有一個(gè)請(qǐng)求處理業(yè)務(wù)。
// 基于Redis的分布式鎖(使用Redisson)
@Autowired
private RedissonClient redissonClient;
public Result processDistributed(String key) {
RLock lock = redissonClient.getLock("lock:" + key);
try {
// 嘗試獲取鎖,最多等待10秒,持有鎖1分鐘
boolean locked = lock.tryLock(10, 60, TimeUnit.SECONDS);
if (locked) {
// 檢查是否已處理(雙重校驗(yàn))
if (isProcessed(key)) {
return Result.success("已處理");
}
// 執(zhí)行業(yè)務(wù)
doBusiness();
markAsProcessed(key); // 標(biāo)記為已處理
return Result.success();
} else {
// 獲取鎖失敗,可能是重復(fù)請(qǐng)求
return Result.fail("操作繁忙,請(qǐng)重試");
}
} finally {
// 釋放鎖(僅釋放自己持有的鎖)
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
選擇建議
- 查詢接口:天然冪等,無(wú)需額外處理。
- 新增操作:優(yōu)先使用 “唯一標(biāo)識(shí) + 數(shù)據(jù)庫(kù) / Redis 去重”。
- 更新操作:優(yōu)先使用 “樂(lè)觀鎖” 或 “狀態(tài)機(jī)”。
- 分布式系統(tǒng):結(jié)合 “分布式鎖” 與 “唯一標(biāo)識(shí)” 確保一致性。
- 高并發(fā)場(chǎng)景:優(yōu)先使用 Redis(性能優(yōu)于數(shù)據(jù)庫(kù))。
需注意:冪等性設(shè)計(jì)需結(jié)合業(yè)務(wù)場(chǎng)景,避免過(guò)度設(shè)計(jì);同時(shí)要處理異常情況(如網(wǎng)絡(luò)超時(shí)),確??蛻舳酥卦嚂r(shí)的正確性。
到此這篇關(guān)于java中接口冪等性的五種實(shí)現(xiàn)方法的文章就介紹到這了,更多相關(guān)java 接口冪等性內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JVM分配和回收堆外內(nèi)存的方式與注意點(diǎn)
JVM啟動(dòng)時(shí)分配的內(nèi)存稱為堆內(nèi)存,與之相對(duì)的,在代碼中還可以使用堆外內(nèi)存,比如Netty,廣泛使用了堆外內(nèi)存,下面這篇文章主要給大家介紹了關(guān)于JVM分配和回收堆外內(nèi)存的方式與注意點(diǎn),需要的朋友可以參考下2022-07-07
必知必會(huì)的SpringBoot實(shí)現(xiàn)熱部署兩種方式
這篇文章主要為大家介紹了必知必會(huì)的SpringBoot實(shí)現(xiàn)熱部署兩種方式詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04
Mybatis-Plus中IdType.AUTO局部配置不生效的問(wèn)題解決
本文主要介紹了Mybatis-Plus中IdType.AUTO局部配置不生效的問(wèn)題解決,數(shù)據(jù)庫(kù)插入數(shù)據(jù)時(shí),id的默認(rèn)生成方式還是雪花算法,局部配置沒(méi)有生效,下面就來(lái)解決一下,感興趣的可以了解一下2023-09-09
Spring boot 應(yīng)用實(shí)現(xiàn)動(dòng)態(tài)刷新配置詳解
這篇文章主要介紹了spring boot 配置動(dòng)態(tài)刷新實(shí)現(xiàn)詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2021-09-09
Java Fluent Mybatis 聚合查詢與apply方法詳解流程篇
Java中常用的ORM框架主要是mybatis, hibernate, JPA等框架。國(guó)內(nèi)又以Mybatis用的多,基于mybatis上的增強(qiáng)框架,又有mybatis plus和TK mybatis等。今天我們介紹一個(gè)新的mybatis增強(qiáng)框架 fluent mybatis關(guān)于聚合查詢、apply方法詳解2021-10-10
Java使用注解實(shí)現(xiàn)BigDecimal的四舍五入
BigDecimal是Java中的一個(gè)類,位于java.math包中,它提供了任意精度的有符號(hào)十進(jìn)制數(shù)字的表示,以及對(duì)這些數(shù)字進(jìn)行算術(shù)運(yùn)算的方法,本文介紹了Java使用注解實(shí)現(xiàn)BigDecimal的四舍五入的相關(guān)知識(shí),需要的朋友可以參考下2024-09-09

