Spring Boot集成MongoDB,如何实现新增卡片自动递增排序序号?
我有一个需求:保存卡片时自动将positionSequence字段递增1,用于卡片排序、调整位置,生成从1开始的序列,仅在新增时生效。
我的Card实体类代码如下:
@Document public class Card { @Id private ObjectId id; private String name; private Long positionSequence = 1L; // increment the position 1 when saved // getter, setter }
由于MongoDB没有JPA的@PrePersist注解,我考虑继承AbstractMongoEventListener,通过BeforeConvertEvent来实现序号递增,代码如下:
@Override public void onBeforeConvert(BeforeConvertEvent<Card> event) { Card card = event.getSource(); if (card.getId() == null) { card.setPositionSequence(positionSequence++); } }
请问这个方案是否可行?
你的这个方案在单实例单线程的场景下是可以正常工作的,但一旦涉及多线程并发写入或者应用重启,就会出现问题。我来详细分析一下:
一、方案的可行之处
- 监听
BeforeConvertEvent的思路是对的:MongoDB中新增实体时id必然为null,更新时id一定存在,这个判断逻辑能准确区分新增和更新操作,符合你“仅在新增时生效”的需求。 - 单线程下序列生成正常:如果你的应用只有一个线程处理卡片新增请求,监听器里的
positionSequence变量会串行递增,能生成从1开始的连续序列。
二、存在的隐患
并发场景下的线程安全问题
如果你的应用是多线程或者分布式部署的,监听器类的成员变量positionSequence会被多个线程同时读写,导致序号重复或者跳号。比如线程A和线程B同时读取到positionSequence=3,然后都把自己的卡片序号设为4,就会出现重复的排序值,破坏序列的唯一性。应用重启后序列重置问题
监听器里的positionSequence是内存变量,一旦应用重启,这个值会回到初始状态(比如你如果没给它设初始值,默认会是0或者1),新生成的卡片序号会和之前已有的卡片重复,完全打乱排序逻辑。
三、更可靠的改进方案
要解决这些问题,我们需要借助MongoDB的原子操作来维护全局唯一的序列,常用的方式是单独维护一个序列集合,每次新增卡片时原子性地获取下一个递增值:
1. 创建序列实体类
@Document(collection = "sequence") public class Sequence { @Id private String id; // 用固定标识,比如"card_position_seq" private Long value; // getter和setter方法 }
2. 修改监听器逻辑
通过MongoTemplate的findAndModify方法实现原子递增,这个操作会在同一个事务里完成查询、递增、返回新值的步骤,完全避免并发问题:
@Autowired private MongoTemplate mongoTemplate; @Override public void onBeforeConvert(BeforeConvertEvent<Card> event) { Card card = event.getSource(); if (card.getId() == null) { // 构造查询条件:找到标识为card_position_seq的序列文档 Query query = new Query(Criteria.where("_id").is("card_position_seq")); // 构造更新操作:把value字段递增1 Update update = new Update().inc("value", 1); // 设置选项:如果序列文档不存在就自动插入,返回更新后的新值 FindAndModifyOptions options = new FindAndModifyOptions() .returnNew(true) .upsert(true); // 执行原子操作,获取最新的序列值 Sequence sequence = mongoTemplate.findAndModify(query, update, options, Sequence.class); // 给新卡片设置序号 card.setPositionSequence(sequence.getValue()); } }
这种方案的优势:
- 完全线程安全,分布式部署下也能保证序号唯一且连续
- 序列值存储在MongoDB集合中,应用重启后不会丢失,序列不会中断
内容的提问来源于stack exchange,提问作者Braian Silva

