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

Spring Boot预约调度验证实现求助:患者同时间段仅可预约一次

Spring框架下预约挂号的时段唯一性验证实现

要确保患者在同一时间段内只能预约一次,核心是检查患者已有预约与新预约的时段是否重叠,同时要处理并发场景防止重复提交。以下是具体实现方案:

1. 数据库表设计

预约表需包含基础字段:

  • patient_id:患者唯一ID(关联患者表)
  • start_time:预约开始时间(建议用datetime类型)
  • end_time:预约结束时间
  • 其他业务字段(如doctor_id、department_id等)

不需要额外的唯一约束,因为时间重叠是逻辑判断,而非固定值的唯一性。

2. 业务层核心验证逻辑

在预约创建的Service方法中,先查询该患者是否存在与新预约时段重叠的记录,有则抛出异常,无则创建预约。

时间重叠的判断规则:新预约的开始时间 < 已有预约的结束时间,且新预约的结束时间 > 已有预约的开始时间——只要满足这个条件,就说明两个预约时段有重叠。

示例Service代码:

@Service
public class AppointmentService {

    private final AppointmentRepository appointmentRepo;

    // 构造注入替代@Autowired(更符合Spring规范)
    public AppointmentService(AppointmentRepository appointmentRepo) {
        this.appointmentRepo = appointmentRepo;
    }

    @Transactional
    public void bookAppointment(AppointmentRequest request) {
        // 检查冲突预约
        List<Appointment> conflicts = appointmentRepo.findConflictingAppointments(
                request.getPatientId(),
                request.getStartTime(),
                request.getEndTime()
        );

        if (!conflicts.isEmpty()) {
            throw new IllegalStateException("当前时段已有预约,无法重复提交");
        }

        // 转换为实体类并保存
        Appointment appointment = new Appointment();
        appointment.setPatientId(request.getPatientId());
        appointment.setStartTime(request.getStartTime());
        appointment.setEndTime(request.getEndTime());
        // 填充其他字段...
        appointmentRepo.save(appointment);
    }
}

3. DAO层实现冲突查询

用Spring Data JPA实现时段重叠查询,有两种方式:

方式一:方法命名规则

利用Spring Data的方法命名自动生成SQL:

public interface AppointmentRepository extends JpaRepository<Appointment, Long> {
    // 对应逻辑:patientId匹配,且已有预约的开始时间 < 新预约结束时间,已有预约的结束时间 > 新预约开始时间
    List<Appointment> findByPatientIdAndStartTimeBeforeAndEndTimeAfter(
            Long patientId,
            LocalDateTime newEndTime,
            LocalDateTime newStartTime
    );
}

方式二:自定义JPQL

如果需要更灵活的查询逻辑,用JPQL编写:

public interface AppointmentRepository extends JpaRepository<Appointment, Long> {

    @Query("SELECT a FROM Appointment a WHERE a.patientId = :patientId " +
           "AND a.startTime < :newEndTime AND a.endTime > :newStartTime")
    List<Appointment> findConflictingAppointments(
            @Param("patientId") Long patientId,
            @Param("newEndTime") LocalDateTime newEndTime,
            @Param("newStartTime") LocalDateTime newStartTime
    );
}

4. 并发场景处理

当多个请求同时提交同一患者的重叠时段预约时,可能出现“查询无冲突→同时保存”的竞态问题,解决方法:

悲观锁方案(推荐)

在查询冲突时添加FOR UPDATE锁,锁定符合条件的记录,确保同一时间只有一个事务能执行后续操作:

@Query("SELECT a FROM Appointment a WHERE a.patientId = :patientId " +
       "AND a.startTime < :newEndTime AND a.endTime > :newStartTime FOR UPDATE")
List<Appointment> findConflictingAppointmentsForUpdate(...);

注意要配合@Transactional注解,确保查询和保存在同一个事务中。

乐观锁方案(可选)

给预约表添加version字段,保存时校验版本号,但这种方式更适合更新场景,对新增预约的并发冲突处理不如悲观锁直接。

5. 异常处理优化

用Spring的全局异常处理器统一处理业务异常,返回友好的前端提示:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(IllegalStateException.class)
    public ResponseEntity<String> handleBusinessErrors(IllegalStateException e) {
        return ResponseEntity.badRequest().body(e.getMessage());
    }
}

注意事项

  • 时间类型优先用LocalDateTime(Java 8+),避免时区转换问题。
  • 事务的传播行为要配置正确,确保锁的范围覆盖查询和保存操作。
  • 测试时一定要覆盖并发场景,比如用JUnit的@RepeatedTest或第三方工具模拟多请求并发提交,验证防重复逻辑生效。

内容的提问来源于stack exchange,提问作者Preethi Rajan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 01:20:37