基于Spring Boot、JPA与PostgreSQL实现单列多序列证书编号方案咨询
实现方案说明
该需求完全可以通过Spring Boot + JPA技术栈实现,以下是两种成熟的落地实现方案:
方案一:基于PostgreSQL独立序列实现(推荐,性能高、并发安全)
本方案利用PostgreSQL原生支持多独立序列的特性,为三类产品分别创建专属序列,序列的自增操作是原子性的,天然避免编号重复问题。
实现步骤
- 第一步:在PostgreSQL中创建3个对应产品的序列,设置不同起始值
-- 第一类产品序列,从001开始 CREATE SEQUENCE IF NOT EXISTS cert_seq_product1 START 1 INCREMENT 1; -- 第二类产品序列,从2000开始 CREATE SEQUENCE IF NOT EXISTS cert_seq_product2 START 2000 INCREMENT 1; -- 第三类产品序列,可根据实际需求调整起始值,示例从5000开始 CREATE SEQUENCE IF NOT EXISTS cert_seq_product3 START 5000 INCREMENT 1;
- 第二步:在Repository中添加查询序列值的原生查询方法
public interface CertRepository extends JpaRepository<CertEntity, Long> { @Query(value = "SELECT nextval(:seqName)", nativeQuery = true) Long getNextSeqValue(String seqName); }
- 第三步:业务层实现编号生成逻辑,在保存数据前赋值证书编号
@Service public class CertService { @Autowired private CertRepository certRepository; @Transactional(rollbackFor = Exception.class) public CertEntity saveCert(CertEntity cert) { // 根据产品类型匹配对应序列名 String seqName = switch (cert.getProductType()) { case 1 -> "cert_seq_product1"; case 2 -> "cert_seq_product2"; case 3 -> "cert_seq_product3"; default -> throw new IllegalArgumentException("不支持的产品类型"); }; // 获取序列下一个值,可根据需求调整格式补前导零 Long nextVal = certRepository.getNextSeqValue(seqName); // 示例格式为固定3位,不足补前导零,如需4位可改为%04d String certNo = String.format("%03d", nextVal); cert.setCertNo(certNo); return certRepository.save(cert); } }
方案优势
实现逻辑简单,序列自增性能极高,无需额外处理并发冲突,适合高并发场景使用。
方案二:基于序列控制表实现(兼容多数据库、扩展性强)
如果后续存在更换数据库的需求,或者产品类型会持续新增,可通过单独的序列控制表存储各产品的当前最大编号,配合乐观锁控制并发。
实现步骤
- 第一步:创建序列控制表并初始化数据
CREATE TABLE cert_seq_config ( id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, product_type INT UNIQUE NOT NULL COMMENT '产品类型', current_max_no INT NOT NULL COMMENT '当前最大编号', version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号' ); -- 初始化三类产品的起始值,current_max_no为上次使用的最大值,首次自增后即为起始值 INSERT INTO cert_seq_config (product_type, current_max_no) VALUES (1, 0), (2, 1999), (3, 4999);
- 第二步:创建序列配置实体类和Repository
@Entity @Table(name = "cert_seq_config") public class CertSeqConfig { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Integer productType; private Integer currentMaxNo; @Version // 乐观锁注解,自动处理版本校验 private Integer version; // 省略getter、setter } public interface CertSeqConfigRepository extends JpaRepository<CertSeqConfig, Long> { Optional<CertSeqConfig> findByProductType(Integer productType); }
- 第三步:业务层添加重试机制,处理乐观锁冲突
@Service public class CertService { @Autowired private CertRepository certRepository; @Autowired private CertSeqConfigRepository seqConfigRepository; // 乐观锁冲突时自动重试最多3次,间隔100ms @Retryable(value = ObjectOptimisticLockingFailureException.class, maxAttempts = 3, backoff = @Backoff(delay = 100)) @Transactional(rollbackFor = Exception.class) public CertEntity saveCert(CertEntity cert) { CertSeqConfig seqConfig = seqConfigRepository.findByProductType(cert.getProductType()) .orElseThrow(() -> new IllegalArgumentException("不支持的产品类型")); // 计算下一个编号 Integer nextNo = seqConfig.getCurrentMaxNo() + 1; // 更新序列最大值 seqConfig.setCurrentMaxNo(nextNo); seqConfigRepository.save(seqConfig); // 赋值证书编号 cert.setCertNo(String.format("%03d", nextNo)); return certRepository.save(cert); } }
方案优势
兼容所有关系型数据库,后续新增产品类型仅需在cert_seq_config表中新增一条记录即可,无需修改数据库对象。
注意事项
- 若需修改证书编号的位数或格式,仅需调整
String.format的格式化规则即可 - 并发量极高的场景优先选择方案一,序列的性能远高于乐观锁方案
- 两种方案的编号生成和数据插入都在同一事务内执行,保证数据一致性
内容的提问来源于stack exchange,提问作者Maverick
相关产品推荐
相关产品推荐

