java中接口幂等性的五种实现方法

 更新时间:2025年09月28日 11:02:28   作者:forever銳  
在java中保证接口幂等性,即多次调用同一接口产生与单次调用相同的结果,不会引发副作用,需要结合业务场景选择合适的方案,下面就来介绍五种实现方法,感兴趣的可以了解一下

在 Java 中保证接口幂等性(即多次调用同一接口产生与单次调用相同的结果,不会引发副作用),需要结合业务场景选择合适的方案。以下是常见的实现方式及技术细节:

1. 基于唯一标识的去重机制

核心思想:为每次请求生成唯一标识(如订单号、请求 ID),服务端通过记录该标识是否已处理,避免重复执行。

实现方式:

数据库唯一约束:将唯一标识作为数据库表的唯一索引,重复请求会触发主键冲突异常,直接返回成功(或错误提示)。

// 示例:订单表唯一索引(order_no)
@Entity
@Table(uniqueConstraints = {@UniqueConstraint(columnNames = "orderNo")})
public class Order {
    @Id
    private Long id;
    private String orderNo; // 唯一订单号(作为幂等标识)
    // 其他字段...
}

// 服务层处理
@Transactional
public Result createOrder(OrderDTO dto) {
    try {
        // 尝试插入订单(依赖数据库唯一约束)
        Order order = new Order();
        order.setOrderNo(dto.getOrderNo());
        orderRepository.save(order);
        // 执行后续业务(如扣减库存)
        return Result.success();
    } catch (DataIntegrityViolationException e) {
        // 唯一约束冲突,说明已处理过
        log.warn("订单已存在: {}", dto.getOrderNo());
        return Result.success(); // 或返回已有结果
    }
}

缓存记录(Redis) :利用 Redis 的SETNX(不存在则设置)特性,判断请求是否已处理。

@Autowired
private StringRedisTemplate redisTemplate;

public Result processRequest(String requestId) {
    // 尝试设置唯一标识,过期时间防止内存溢出
    Boolean isFirst = redisTemplate.opsForValue().setIfAbsent(
        "idempotent:" + requestId, 
        "processed", 
        1, TimeUnit.HOURS
    );
    
    if (Boolean.TRUE.equals(isFirst)) {
        // 首次请求,执行业务逻辑
        doBusiness();
        return Result.success();
    } else {
        // 重复请求,返回已有结果
        return Result.success("已处理");
    }
}

2. 令牌(Token)机制

核心思想:客户端先向服务端申请令牌,请求接口时携带令牌,服务端验证令牌有效性后处理业务,并标记令牌为已使用。

实现流程:

  1. 客户端请求获取令牌(服务端生成令牌并存储到 Redis)。
  2. 客户端携带令牌调用业务接口。
  3. 服务端校验令牌:存在则处理业务并删除令牌;不存在则拒绝。
// 生成令牌
public String generateToken() {
    String token = UUID.randomUUID().toString();
    redisTemplate.opsForValue().set("token:" + token, "valid", 30, TimeUnit.MINUTES);
    return token;
}

// 校验令牌并处理业务
public Result doBusiness(String token, BusinessDTO dto) {
    // 删除令牌(原子操作,确保唯一处理)
    Boolean isValid = redisTemplate.delete("token:" + token);
    if (Boolean.TRUE.equals(isValid)) {
        // 令牌有效,执行业务
        process(dto);
        return Result.success();
    } else {
        // 令牌无效(已使用或过期)
        return Result.fail("重复请求");
    }
}

3. 乐观锁机制

核心思想:适用于更新操作,通过版本号控制,确保只有版本匹配时才执行更新,避免重复更新。

@Entity
public class Product {
    @Id
    private Long id;
    private Integer stock; // 库存
    private Integer version; // 版本号
}

@Transactional
public Result reduceStock(Long productId, Integer quantity) {
    // 查询商品及当前版本
    Product product = productRepository.findById(productId)
        .orElseThrow(() -> new RuntimeException("商品不存在"));
    
    // 检查库存
    if (product.getStock() < quantity) {
        return Result.fail("库存不足");
    }
    
    // 乐观锁更新(where条件包含版本号)
    int rows = productRepository.reduceStock(
        productId, 
        quantity, 
        product.getVersion() // 当前版本
    );
    
    if (rows > 0) {
        return Result.success();
    } else {
        // 版本不匹配,说明已被其他请求处理
        return Result.fail("操作冲突,请重试");
    }
}

// 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. 状态机控制

核心思想:通过状态流转约束,确保接口只能在特定状态下执行,避免重复操作(如订单状态从 “待支付” 到 “已支付” 的单向流转)。

public enum OrderStatus {
    CREATED(1, "待支付"),
    PAID(2, "已支付"),
    CANCELLED(3, "已取消");
    
    private int code;
    private String desc;
    // 构造器、getter...
}

@Transactional
public Result payOrder(Long orderId) {
    Order order = orderRepository.findById(orderId)
        .orElseThrow(() -> new RuntimeException("订单不存在"));
    
    // 仅允许“待支付”状态执行支付
    if (order.getStatus() != OrderStatus.CREATED) {
        log.warn("订单状态异常: {}", orderId);
        return Result.success("订单已处理"); // 重复支付请求直接返回成功
    }
    
    // 执行支付逻辑(如调用支付网关)
    boolean paySuccess = paymentGateway.pay(order);
    if (paySuccess) {
        order.setStatus(OrderStatus.PAID);
        orderRepository.save(order);
        return Result.success();
    } else {
        return Result.fail("支付失败");
    }
}

5. 分布式锁(高并发场景)

核心思想:在分布式系统中,通过分布式锁(如 Redis、ZooKeeper)确保同一时间只有一个请求处理业务。

// 基于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) {
            // 检查是否已处理(双重校验)
            if (isProcessed(key)) {
                return Result.success("已处理");
            }
            // 执行业务
            doBusiness();
            markAsProcessed(key); // 标记为已处理
            return Result.success();
        } else {
            // 获取锁失败,可能是重复请求
            return Result.fail("操作繁忙,请重试");
        }
    } finally {
        // 释放锁(仅释放自己持有的锁)
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
}

选择建议

  • 查询接口:天然幂等,无需额外处理。
  • 新增操作:优先使用 “唯一标识 + 数据库 / Redis 去重”。
  • 更新操作:优先使用 “乐观锁” 或 “状态机”。
  • 分布式系统:结合 “分布式锁” 与 “唯一标识” 确保一致性。
  • 高并发场景:优先使用 Redis(性能优于数据库)。

需注意:幂等性设计需结合业务场景,避免过度设计;同时要处理异常情况(如网络超时),确保客户端重试时的正确性。

到此这篇关于java中接口幂等性的五种实现方法的文章就介绍到这了,更多相关java 接口幂等性内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • JVM分配和回收堆外内存的方式与注意点

    JVM分配和回收堆外内存的方式与注意点

    JVM启动时分配的内存称为堆内存,与之相对的,在代码中还可以使用堆外内存,比如Netty,广泛使用了堆外内存,下面这篇文章主要给大家介绍了关于JVM分配和回收堆外内存的方式与注意点,需要的朋友可以参考下
    2022-07-07
  • 必知必会的SpringBoot实现热部署两种方式

    必知必会的SpringBoot实现热部署两种方式

    这篇文章主要为大家介绍了必知必会的SpringBoot实现热部署两种方式详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-04-04
  • Mybatis-Plus中IdType.AUTO局部配置不生效的问题解决

    Mybatis-Plus中IdType.AUTO局部配置不生效的问题解决

    本文主要介绍了Mybatis-Plus中IdType.AUTO局部配置不生效的问题解决,数据库插入数据时,id的默认生成方式还是雪花算法,局部配置没有生效,下面就来解决一下,感兴趣的可以了解一下
    2023-09-09
  • java 打造阻塞式线程池的实例详解

    java 打造阻塞式线程池的实例详解

    这篇文章主要介绍了java 打造阻塞式线程池的实例详解的相关资料,这里提供实例帮助大家,理解这部分知识,需要的朋友可以参考下
    2017-07-07
  • Spring boot 应用实现动态刷新配置详解

    Spring boot 应用实现动态刷新配置详解

    这篇文章主要介绍了spring boot 配置动态刷新实现详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2021-09-09
  • java使用Graphics2D绘图/画图方式

    java使用Graphics2D绘图/画图方式

    这篇文章主要介绍了java使用Graphics2D绘图/画图方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • Java Fluent Mybatis 聚合查询与apply方法详解流程篇

    Java Fluent Mybatis 聚合查询与apply方法详解流程篇

    Java中常用的ORM框架主要是mybatis, hibernate, JPA等框架。国内又以Mybatis用的多,基于mybatis上的增强框架,又有mybatis plus和TK mybatis等。今天我们介绍一个新的mybatis增强框架 fluent mybatis关于聚合查询、apply方法详解
    2021-10-10
  • 6种方法初始化JAVA中的list集合

    6种方法初始化JAVA中的list集合

    这篇文章主要介绍了6种方法初始化JAVA中的list集合,文中讲解非常详细,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-07-07
  • 深入解读MVC模式和三层架构

    深入解读MVC模式和三层架构

    这篇文章主要介绍了深入解读MVC模式和三层架构,三层架构就是为了符合“高内聚,低耦合”思想,把各个功能模块划分为表示层(UI)、业务逻辑层(BLL)和数据访问层(DAL)的三层架构,各层之间采用接口相互访问,需要的朋友可以参考下
    2023-04-04
  • Java使用注解实现BigDecimal的四舍五入

    Java使用注解实现BigDecimal的四舍五入

    BigDecimal是Java中的一个类,位于java.math包中,它提供了任意精度的有符号十进制数字的表示,以及对这些数字进行算术运算的方法,本文介绍了Java使用注解实现BigDecimal的四舍五入的相关知识,需要的朋友可以参考下
    2024-09-09

最新评论