Spring Boot集成DynamoDB如何实现主键自增并补全序列空缺值
DynamoDB自增主键补空缺实现方案(Spring Boot版)
DynamoDB本身没有原生的自增主键能力,也没有内置的空缺ID补全逻辑,要实现需求可以通过单独维护序列状态的方案实现,具体步骤如下:
1. 新建序列管理表
先创建一张专门维护ID状态的DynamoDB表,例如命名为sequence_table,表结构如下:
- 分区键:
sequence_name(字符串类型,存储对应业务表的唯一序列标识,比如业务表为事件表,这里可填event_id_seq) - 通用属性:
current_max_id:数值类型,记录当前已分配过的最大IDused_ids:数值集合类型,记录所有已经被使用的ID
2. 核心实现逻辑
ID分配逻辑遵循「优先补空缺,无空缺则递增」的规则,同时通过乐观锁避免并发场景下的ID重复问题:
- 读取序列表中对应业务的
current_max_id和used_ids - 遍历1到
current_max_id的范围,找到第一个不在used_ids中的数值作为待分配ID - 如果未找到空缺ID,则将
current_max_id加1作为新的待分配ID - 将新分配的ID加入
used_ids集合,若为新增的最大ID则同步更新current_max_id - 业务数据删除时,需将对应ID从
used_ids集合中移除,方便后续分配时补全空缺
3. Spring Boot代码实现
3.1 引入依赖
以AWS Java SDK v2为例,Maven依赖配置如下:
<dependencies> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>dynamodb</artifactId> <version>2.20.0</version> </dependency> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>enhanced-dynamodb</artifactId> <version>2.20.0</version> </dependency> </dependencies>
3.2 序列实体类
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; import java.util.Set; @DynamoDbBean public class Sequence { private String sequenceName; private Long currentMaxId; private Set<Long> usedIds; @DynamoDbPartitionKey public String getSequenceName() { return sequenceName; } public void setSequenceName(String sequenceName) { this.sequenceName = sequenceName; } public Long getCurrentMaxId() { return currentMaxId; } public void setCurrentMaxId(Long currentMaxId) { this.currentMaxId = currentMaxId; } public Set<Long> getUsedIds() { return usedIds; } public void setUsedIds(Set<Long> usedIds) { this.usedIds = usedIds; } }
3.3 ID生成服务
import org.springframework.stereotype.Service; import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient; import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable; import software.amazon.awssdk.enhanced.dynamodb.Expression; import software.amazon.awssdk.enhanced.dynamodb.Key; import software.amazon.awssdk.enhanced.dynamodb.model.UpdateItemEnhancedRequest; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; import software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException; import java.util.HashSet; import java.util.Set; @Service public class IdGeneratorService { private static final String BUSINESS_SEQ_NAME = "event_id_seq"; private final DynamoDbTable<Sequence> sequenceTable; public IdGeneratorService(DynamoDbEnhancedClient enhancedClient) { this.sequenceTable = enhancedClient.table("sequence_table", software.amazon.awssdk.enhanced.dynamodb.TableSchema.fromBean(Sequence.class)); } public Long generateNextId() { int retryTimes = 3; while (retryTimes > 0) { Sequence currentSeq = sequenceTable.getItem(Key.builder() .partitionValue(BUSINESS_SEQ_NAME) .build()); // 序列不存在则初始化 if (currentSeq == null) { Sequence initSeq = new Sequence(); initSeq.setSequenceName(BUSINESS_SEQ_NAME); initSeq.setCurrentMaxId(1L); initSeq.setUsedIds(Set.of(1L)); sequenceTable.putItem(initSeq); return 1L; } Long currentMaxId = currentSeq.getCurrentMaxId(); Set<Long> usedIds = currentSeq.getUsedIds(); Long nextId = null; // 查找最小空缺ID for (long i = 1; i <= currentMaxId; i++) { if (!usedIds.contains(i)) { nextId = i; break; } } // 无空缺则取最大ID+1 if (nextId == null) { nextId = currentMaxId + 1; currentSeq.setCurrentMaxId(nextId); } // 更新已使用ID集合 Set<Long> newUsedIds = new HashSet<>(usedIds); newUsedIds.add(nextId); currentSeq.setUsedIds(newUsedIds); try { // 乐观锁条件更新,避免并发冲突 sequenceTable.updateItem(UpdateItemEnhancedRequest.builder(Sequence.class) .item(currentSeq) .conditionExpression(Expression.builder() .expression("currentMaxId = :prevMax") .putExpressionValue(":prevMax", AttributeValue.fromN(currentMaxId.toString())) .build()) .build()); return nextId; } catch (ConditionalCheckFailedException e) { // 并发冲突重试 retryTimes--; } } throw new RuntimeException("ID生成失败,并发冲突重试次数耗尽"); } }
3.4 数据删除同步逻辑
删除业务数据时需要同步更新序列表的已使用ID集合,方便后续补全空缺:
import org.springframework.stereotype.Service; import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable; import software.amazon.awssdk.enhanced.dynamodb.Key; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest; import java.util.List; import java.util.Map; @Service public class BusinessDataService { private final DynamoDbTable<Event> eventTable; private final DynamoDbTable<Sequence> sequenceTable; private static final String BUSINESS_SEQ_NAME = "event_id_seq"; public BusinessDataService(DynamoDbTable<Event> eventTable, DynamoDbTable<Sequence> sequenceTable) { this.eventTable = eventTable; this.sequenceTable = sequenceTable; } public void deleteEvent(Long eventId) { // 删除业务数据 eventTable.deleteItem(Key.builder().partitionValue(eventId).build()); // 从已使用ID集合中移除对应ID UpdateItemRequest updateRequest = UpdateItemRequest.builder() .tableName("sequence_table") .key(Map.of("sequenceName", AttributeValue.fromS(BUSINESS_SEQ_NAME))) .updateExpression("DELETE used_ids :removeId") .expressionAttributeValues(Map.of(":removeId", AttributeValue.fromNs(List.of(eventId.toString())))) .build(); sequenceTable.updateItem(updateRequest); } }
4. 优化建议
- 数据量大、空缺ID多的场景下,可以在序列表中新增
available_ids集合属性,删除数据时直接把ID加入该集合,分配ID时优先从available_ids取,无需遍历找空缺,性能更高 - 并发量极高的场景可以适当调大重试次数,或者引入分布式锁进一步降低冲突概率
- 如果对ID连续性要求不高,建议只做普通自增不补空缺,性能会更好
内容的提问来源于stack exchange,提问作者Codshark11
相关产品推荐
相关产品推荐

