如何在Spring Boot中生成自定义患者ID并持久化到数据库?
问题
患者注册类需要包含唯一的patientId,格式规则为:P+当前年月日+4位当日序列号,示例如下:
- 当日第1位患者:
P202407280001(前缀P + 2024年+07月+28日 + 0001序列号) - 当日第200位患者:
P202407280200 - 次日首位患者:
P202407290001(序列号每日重置并自增)
请问:
- 这个生成逻辑能否保证ID唯一性?
- 如何以健壮高效的方式实现该逻辑?
现有代码
实体类代码
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:数据库单独维护每日序列号表
这是最通用、易维护的方案:
- 创建一个存储每日序列号的实体类和表:
@Entity @Data public class DailyPatientSerial { @Id private LocalDate date; private int currentSerial; }
- 修改服务类,通过事务原子更新序列号:
@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
相关产品推荐
相关产品推荐

