You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Spring Boot中生成自定义患者ID并持久化到数据库?

问题

患者注册类需要包含唯一的patientId,格式规则为:P+当前年月日+4位当日序列号,示例如下:

  • 当日第1位患者:P202407280001(前缀P + 2024年+07月+28日 + 0001序列号)
  • 当日第200位患者:P202407280200
  • 次日首位患者:P202407290001(序列号每日重置并自增)

请问:

  1. 这个生成逻辑能否保证ID唯一性?
  2. 如何以健壮高效的方式实现该逻辑?

现有代码

实体类代码

import lombok.Data;
import java.time.LocalDate;

@Entity
@Data
public class Patient {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true, nullable = false)
    private String patientId;

    private String name;
    private Integer age;
    private String address;
    private String contact;
}

服务类代码

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Optional;

@Service
public class PatientService {

    private final PatientRepository patientRepository;

    @Autowired
    public PatientService(PatientRepository patientRepository) {
        this.patientRepository = patientRepository;
    }

    public Patient savePatient(Patient patient) {
        // Generate patient ID
        String patientIdPrefix = "P";
        LocalDate currentDate = LocalDate.now();
        String formattedDate = currentDate.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        String serialNumber = generateSerialNumber(currentDate);
        patient.setPatientId(patientIdPrefix + formattedDate + serialNumber);
        return patientRepository.save(patient);
    }

    public List<Patient> getAllPatients() {
        return patientRepository.findAll();
    }

    public Optional<Patient> getPatientById(Long id) {
        return patientRepository.findById(id);
    }

    public Patient updatePatient(Patient patient) {
        return patientRepository.save(patient);
    }

    public void deletePatient(Long id) {
        patientRepository.deleteById(id);
    }

    private String generateSerialNumber(LocalDate currentDate) {    
        static int serial = 1;
        String formattedSerial = String.format("%04d", serial++);
        return formattedSerial;
    }
}

解答

现有生成逻辑的唯一性问题

当前逻辑完全无法保证ID唯一性,核心问题有三个:

  • 集群/重启失效:static变量是进程内的,多节点部署时每个节点的序列号独立;服务重启后static变量重置为1,当天已有患者的话会重复生成相同ID。
  • 并发冲突:static变量自增不是原子操作,高并发下多个请求同时调用会导致序列号重复。
  • 日期判断缺失:方法里没有校验当前日期,跨天后不会重置序列号,会继续递增,不符合规则。

健壮高效的实现方案

核心思路是依托数据库的原子性操作维护每日序列号,避免内存变量的线程安全和集群问题。

方案1:数据库单独维护每日序列号表

这是最通用、易维护的方案:

  1. 创建一个存储每日序列号的实体类和表:
@Entity
@Data
public class DailyPatientSerial {
    @Id
    private LocalDate date;
    private int currentSerial;
}
  1. 修改服务类,通过事务原子更新序列号:
@Service
public class PatientService {
    private final PatientRepository patientRepository;
    private final DailyPatientSerialRepository serialRepository;
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");

    @Autowired
    public PatientService(PatientRepository patientRepository, DailyPatientSerialRepository serialRepository) {
        this.patientRepository = patientRepository;
        this.serialRepository = serialRepository;
    }

    @Transactional
    public Patient savePatient(Patient patient) {
        LocalDate today = LocalDate.now();
        // 原子获取并递增当日序列号:不存在则初始化,存在则+1
        DailyPatientSerial serial = serialRepository.findById(today)
                .orElseGet(() -> {
                    DailyPatientSerial newSerial = new DailyPatientSerial();
                    newSerial.setDate(today);
                    newSerial.setCurrentSerial(0);
                    return newSerial;
                });
        serial.setCurrentSerial(serial.getCurrentSerial() + 1);
        serialRepository.save(serial);

        // 生成符合规则的patientId
        String dateStr = today.format(DATE_FORMATTER);
        String serialStr = String.format("%04d", serial.getCurrentSerial());
        patient.setPatientId("P" + dateStr + serialStr);
        return patientRepository.save(patient);
    }

    // 原有其他方法保持不变...
}

优势:

  • 依托数据库事务,天然支持集群和高并发,不会出现重复序列号。
  • 自动识别日期,跨天后自动从1开始计数。
  • 服务重启后从数据库读取最新状态,不会丢失序列号进度。

额外优化点

  • 保留patientId字段的唯一约束@Column(unique = true, nullable = false),作为最后一道防线:即使生成逻辑出问题,数据库会抛出唯一约束异常,避免重复数据入库。
  • 如果每日患者数量可能超过9999,可将序列号位数从4位调整为5位或更多。

内容的提问来源于stack exchange,提问作者Mahadi Hasan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 13:34:53