SpringBoot集成OptaPlanner与使用指南

 更新时间:2026年02月06日 14:35:16   作者:清风亦可追  
本文详细介绍如何在Spring Boot项目中集成OptaPlanner约束求解器,并实现复杂值班排班系统,本文结合实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧

本文基于IBMS项目中的值班排班系统实现,详细介绍如何在Spring Boot项目中集成OptaPlanner,并构建复杂的约束求解系统。

OptaPlanner简介

什么是OptaPlanner?

OptaPlanner 是一个开源的约束满足问题求解器(Constraint Satisfaction Problem Solver),由JBoss社区开发。它能够:

  • 🎯 快速找到复杂约束优化问题的最优或近似最优解
  • 🔧 支持硬约束(必须满足)和软约束(尽量满足)
  • ⚡ 提供多种求解算法(局部搜索、遗传算法、模拟退火等)
  • 🌍 被广泛应用于排班、路线规划、资源分配等领域

典型应用场景

场景描述难度
员工排班在满足各种约束的情况下分配班次中等
车队路线规划优化配送路线
会议日程安排安排会议时间和地点中等
医院值班医护人员值班分配
考场座位分配考生与考场的最优分配中等

集成步骤

1. 添加依赖

pom.xml 中添加OptaPlanner依赖:

<dependency>
    <groupId>org.optaplanner</groupId>
    <artifactId>optaplanner-core</artifactId>
    <version>7.47.0.Final</version>
</dependency>

版本选择建议:

  • 7.x.x - 稳定版本,性能好,文档完善
  • 8.x.x - 新功能,更好的API设计
  • 建议根据JDK版本选择:JDK 8-11 用 7.x,JDK 11+ 用 8.x
  • 这里选择了7.x.x版本是因为项目的JDK版本限制

2. 创建求解配置文件

src/main/resources/planner/solverConfig.xml 创建配置文件:

<solver>
    <!-- 定义求解的问题类(Solution) -->
    <solutionClass>org.jeecg.modules.planner.Roster</solutionClass>
    <!-- 定义规划实体类 -->
    <entityClass>org.jeecg.modules.planner.ShiftAssignment</entityClass>
    <!-- 运行模式:FAST_ASSERT(快速调试)、FULL_ASSERT(完整校验)、NON_ASSERT(生产环境) -->
    <environmentMode>FAST_ASSERT</environmentMode>
    <!-- 定义约束提供者 -->
    <scoreDirectorFactory>
        <constraintProviderClass>org.jeecg.modules.planner.RosterConstraintProvider</constraintProviderClass>
    </scoreDirectorFactory>
    <!-- 求解时间限制 -->
    <termination>
        <secondsSpentLimit>10</secondsSpentLimit>
        <bestScoreLimit>0hard/-5000soft</bestScoreLimit>
    </termination>
    <!-- 构造启发阶段 -->
    <constructionHeuristic>
        <constructionHeuristicType>FIRST_FIT_DECREASING</constructionHeuristicType>
        <entitySorterManner>NONE</entitySorterManner>
        <valueSorterManner>NONE</valueSorterManner>
    </constructionHeuristic>
    <!-- 局部搜索阶段 -->
    <localSearch>
        <acceptor>
            <acceptorType>SIMULATED_ANNEALING</acceptorType>
            <simulatedAnnealingStartingTemperature>2hard/1000soft</simulatedAnnealingStartingTemperature>
        </acceptor>
        <forager>
            <acceptedCountLimit>5000</acceptedCountLimit>
        </forager>
    </localSearch>
</solver>

3. Spring Boot集成配置

创建 @Configuration 类(可选,用于高级配置):

@Configuration
public class OptaPlannerConfig {
    @Bean
    public SolverFactory<Roster> solverFactory() {
        return SolverFactory.createFromXmlResource("./planner/solverConfig.xml");
    }
}

核心概念

📌 关键术语解释

1.PlanningSolution(规划求解方案)

  • 代表整个优化问题
  • 包含规划实体集合和问题事实
  • 有一个规划评分
@PlanningSolution
public class Roster {
    // 问题事实:不被规划器修改
    @ProblemFactProperty
    private ConstraintConfig constraintConfig;
    // 规划实体集合:规划器需要分配的值域变量
    @PlanningEntityCollectionProperty
    private List<ShiftAssignment> shiftAssignmentList;
    // 值域范围:规划变量的可选值
    @ValueRangeProvider(id = "employeeRange")
    private List<Employee> employeeList;
    // 规划评分:衡量方案质量
    @PlanningScore
    private HardSoftScore score;
}

2.PlanningEntity(规划实体)

  • 代表需要被优化分配的对象
  • 包含规划变量
@PlanningEntity
public class ShiftAssignment {
    private Shift shift;  // 班次(不变)
    @PlanningVariable(valueRangeProviderRefs = "employeeRange")
    private Employee employee;  // 需要分配的员工
}

3.PlanningVariable(规划变量)

  • 是规划器需要分配的值
  • 从值域提供者中获取可选值
@PlanningVariable(valueRangeProviderRefs = "employeeRange")
private Employee employee;

4.ConstraintProvider(约束提供者)

  • 定义所有约束规则
  • 硬约束:违反会使评分不可行
  • 软约束:违反会降低评分
public class RosterConstraintProvider implements ConstraintProvider {
    @Override
    public Constraint[] defineConstraints(ConstraintFactory factory) {
        return new Constraint[]{
            // 硬约束
            shiftMustHaveAssignment(factory),
            // 软约束
            balanceWorkload(factory)
        };
    }
}

5.Score(评分)

  • HardSoftScore - 分离硬软约束评分
  • 格式:Xhard/Ysoft (例如:0hard/-500soft)
  • 评分越高越好,最优为 0hard/0soft

实现详解

1. 定义域模型

Employee(员工)

public class Employee {
    private String id;                           // 员工ID
    private String name;                         // 员工姓名
    private EmployeePreference preference;       // 员工偏好
    private String availableShifts;              // 可上班次(逗号分隔)
    private String departId;                     // 所属部门
    public Employee(String id, String name, EmployeePreference preference,
                   String availableShifts, String departId) {
        this.id = id;
        this.name = name;
        this.preference = preference;
        this.availableShifts = availableShifts;
        this.departId = departId;
    }
}

说明:

  • availableShifts 存储员工可以上的班次,格式为班次ID的逗号分隔列表
  • preference 记录员工个人偏好(如不想上夜班等)

EmployeePreference(员工偏好)

public class EmployeePreference {
    private boolean avoidConsecutiveWork;  // 不能连续排班
    private boolean avoidWorkday;          // 工作日不能排班
    private boolean avoidHoliday;          // 节假日不能排班
}

Shift(班次)

public class Shift {
    private LocalDate date;           // 班次日期
    private boolean isHoliday;        // 是否为节假日
    private ShiftType type;           // 班次类型(早/中/晚)
    private boolean isNight;          // 是否为夜班
    private int requiredEmployees;    // 需要的员工数
}

ShiftType(班次类型枚举)

public enum ShiftType {
    MORNING(1),  // 早班
    NOON(2),     // 中班
    NIGHT(3);    // 晚班
    private final int order;
    ShiftType(int order) { this.order = order; }
    public int getOrder() { return order; }
}

ShiftAssignment(班次分配 - 规划实体)

@PlanningEntity
public class ShiftAssignment {
    private Shift shift;
    @PlanningVariable(valueRangeProviderRefs = "employeeRange")
    private Employee employee;
    public ShiftAssignment(Shift shift, Employee employee) {
        this.shift = shift;
        this.employee = employee;
    }
}

Roster(排班表 - 求解方案)

@PlanningSolution
public class Roster {
    @ProblemFactProperty
    private ConstraintConfig constraintConfig;
    @PlanningEntityCollectionProperty
    private List<ShiftAssignment> shiftAssignmentList;
    @ValueRangeProvider(id = "employeeRange")
    private List<Employee> employeeList;
    @PlanningScore
    private HardSoftScore score;
    private List<Shift> shiftList;
}

2. 数据准备 - RosterGenerator

@Component
public class RosterGenerator {
    @Resource
    private IDutyStaffService dutyStaffService;
    @Resource
    private IDutyHolidayService dutyHolidayService;
    @Resource
    private IDutyShiftService dutyShiftService;
    @Resource
    private ISysUserService sysUserService;
    public List<Roster> loadRosterFromDB(ConstraintConfig config) {
        // 1. 加载所有班次
        List<DutyShift> shiftList = dutyShiftService.list();
        Map<String, Integer> shiftMap = shiftList.stream()
            .collect(Collectors.toMap(DutyShift::getId, DutyShift::getOrder));
        config.setShiftMap(shiftMap);
        // 2. 获取值班部门的所有员工
        List<String> departIds = config.getDepartIds();
        LambdaQueryWrapper<DutyStaff> staffQueryWrapper = 
            new LambdaQueryWrapper<DutyStaff>().in(DutyStaff::getDepartId, departIds);
        List<DutyStaff> staffs = dutyStaffService.list(staffQueryWrapper);
        // 3. 生成排班期间内的所有班次
        Date startDate = config.getStartDate();
        Date endDate = config.getEndDate();
        LocalDate start = startDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
        LocalDate end = endDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
        List<DutyHoliday> holidays = dutyHolidayService.list(
            new LambdaQueryWrapper<DutyHoliday>()
                .between(DutyHoliday::getCalendarDate, startDate, endDate)
        );
        ArrayList<Shift> shifts = new ArrayList<>();
        for (LocalDate date = start; !date.isAfter(end); date = date.plusDays(1)) {
            // 根据配置决定是否跳过特定日期
            if (shouldSkipDate(date, holidays, config)) continue;
            // 为该日期生成三个班次
            boolean isHoliday = isHoliday(date, holidays);
            shifts.add(new Shift(date, isHoliday, ShiftType.MORNING, false));
            shifts.add(new Shift(date, isHoliday, ShiftType.NOON, false));
            shifts.add(new Shift(date, isHoliday, ShiftType.NIGHT, true));
        }
        // 4. 为每个部门构造Roster对象
        ArrayList<Roster> rosters = new ArrayList<>();
        for (String departId : departIds) {
            // 获取该部门的员工
            List<Employee> employeeList = staffs.stream()
                .filter(staff -> staff.getDepartId().equals(departId))
                .map(staff -> new Employee(
                    staff.getUserId(),
                    sysUser.getRealname(),
                    buildPreference(staff),
                    staff.getShiftId(),
                    departId
                ))
                .collect(Collectors.toList());
            if (employeeList.isEmpty()) continue;
            // 初始分配(随机轮转)
            List<ShiftAssignment> assignments = new ArrayList<>();
            for (int i = 0; i < shifts.size(); i++) {
                int selectedIndex = (i + new Random().nextInt(employeeList.size())) 
                    % employeeList.size();
                assignments.add(new ShiftAssignment(
                    shifts.get(i),
                    employeeList.get(selectedIndex)
                ));
            }
            Roster roster = new Roster();
            roster.setConstraintConfig(config);
            roster.setEmployeeList(employeeList);
            roster.setShiftList(shifts);
            roster.setShiftAssignmentList(assignments);
            rosters.add(roster);
        }
        return rosters;
    }
}

约束定义

RosterConstraintProvider 详解

@Slf4j
public class RosterConstraintProvider implements ConstraintProvider {
    @Override
    public Constraint @NonNull [] defineConstraints(@NonNull ConstraintFactory factory) {
        return new Constraint[]{
            // 硬约束
            shiftMustHaveAssignment(factory),         // ① 每个班次必须有员工
            mustBeAvailableForShift(factory),         // ② 员工只能上可分配班次
            avoidConsecutiveShifts(factory),          // ③ 避免连续排班
            avoidNightShiftConsecutive(factory),      // ④ 避免连续夜班
            // 软约束
            balanceWorkload(factory),                 // ⑤ 平衡工作量
            encourageMultipleEmployees(factory),      // ⑥ 鼓励多员工参与
            avoidHoliday(factory),                    // ⑦ 避免节假日排班
            avoidWorkday(factory),                    // ⑧ 避免工作日排班
            avoidClusteredAssignments(factory)        // ⑨ 避免排班集中
        };
    }
    /** ① 硬约束:每个班次必须有员工分配 */
    private Constraint shiftMustHaveAssignment(ConstraintFactory factory) {
        return factory.from(ShiftAssignment.class)
                .filter(sa -> sa.getEmployee() == null)
                .penalize("班次必须有员工分配", HardSoftScore.ofHard(1));
    }
    /** ② 硬约束:按照员工可排班班次排班 */
    private Constraint mustBeAvailableForShift(ConstraintFactory factory) {
        return factory.from(ShiftAssignment.class)
                .filter(sa -> sa.getEmployee() != null)
                .join(factory.from(ConstraintConfig.class))
                .filter((assignment, cfg) -> {
                    Employee employee = assignment.getEmployee();
                    Shift shift = assignment.getShift();
                    String available = employee.getAvailableShifts();
                    if (available == null || available.isEmpty()) {
                        return true;  // 无法分配
                    }
                    List<Integer> orderList = Arrays.stream(available.split(","))
                            .map(cfg.getShiftMap()::get)
                            .filter(Objects::nonNull)
                            .collect(Collectors.toList());
                    return !orderList.contains(shift.getType().getOrder());
                })
                .penalize("按照员工可排班班次排班", HardSoftScore.ofHard(1));
    }
    /** ③ 硬约束:避免连续排班 */
    private Constraint avoidConsecutiveShifts(ConstraintFactory factory) {
        return factory.from(ShiftAssignment.class)
                .filter(sa -> sa.getEmployee() != null)
                .join(ShiftAssignment.class,
                        Joiners.equal(ShiftAssignment::getEmployee),
                        Joiners.lessThan(sa -> sa.getShift().getDate()))
                .join(factory.from(ConstraintConfig.class))
                .filter((a, b, cfg) -> {
                    // 检查是否同一员工的连续排班
                    boolean isCfgConstraint = cfg.isAvoidConsecutiveShifts();
                    boolean isPreference = cfg.isAllowPersonalPreference() && 
                        a.getEmployee().getPreference().isAvoidConsecutiveWork();
                    if (!isCfgConstraint && !isPreference) return false;
                    long daysDiff = ChronoUnit.DAYS.between(
                        a.getShift().getDate(), 
                        b.getShift().getDate()
                    );
                    // 同一天相邻班次或连续天排班
                    return (daysDiff == 0 && 
                            Math.abs(a.getShift().getType().getOrder() - 
                                    b.getShift().getType().getOrder()) == 1) ||
                           (daysDiff == 1 &&
                            Math.abs(a.getShift().getType().getOrder() - 
                                    b.getShift().getType().getOrder()) <= 1);
                })
                .penalize("避免连续排班", HardSoftScore.ofHard(1));
    }
    /** ④ 硬约束:避免连续夜班 */
    private Constraint avoidNightShiftConsecutive(ConstraintFactory factory) {
        return factory.from(ShiftAssignment.class)
                .filter(sa -> sa.getEmployee() != null)
                .join(ShiftAssignment.class,
                        Joiners.equal(ShiftAssignment::getEmployee),
                        Joiners.lessThan(sa -> sa.getShift().getDate()))
                .join(factory.from(ConstraintConfig.class))
                .filter((a, b, cfg) -> 
                    cfg.isAvoidConsecutiveNightShifts() && 
                    a.getShift().isNight() && 
                    b.getShift().isNight()
                )
                .penalize("避免连续夜班", HardSoftScore.ofHard(1));
    }
    /** ⑤ 软约束:平衡排班量 */
    private Constraint balanceWorkload(ConstraintFactory factory) {
        return factory.from(ShiftAssignment.class)
                .filter(sa -> sa.getEmployee() != null)
                .groupBy(ShiftAssignment::getEmployee, ConstraintCollectors.count())
                .join(factory.from(ConstraintConfig.class))
                .penalize("平衡排班量", HardSoftScore.ofSoft(1000),
                        (employee, count, cfg) -> 
                            cfg.isBalanceWorkload() ? Math.abs(count - 5) : 0);
    }
    /** ⑥ 软约束:鼓励多员工参与 */
    private Constraint encourageMultipleEmployees(ConstraintFactory factory) {
        return factory.from(ShiftAssignment.class)
                .filter(sa -> sa.getEmployee() != null)
                .groupBy(ShiftAssignment::getEmployee)
                .reward("鼓励多员工参与", HardSoftScore.ofSoft(500));
    }
    /** ⑦ 软约束:避免节假日排班 */
    private Constraint avoidHoliday(ConstraintFactory factory) {
        return factory.from(ShiftAssignment.class)
                .filter(sa -> sa.getEmployee() != null)
                .join(factory.from(ConstraintConfig.class))
                .filter((sa, cfg) -> 
                    (cfg.isAvoidHoliday() || 
                     (cfg.isAllowPersonalPreference() && 
                      sa.getEmployee().getPreference().isAvoidHoliday())) && 
                    sa.getShift().isHoliday()
                )
                .penalize("避免节假日排班", HardSoftScore.ofSoft(1));
    }
    /** ⑧ 软约束:避免工作日排班 */
    private Constraint avoidWorkday(ConstraintFactory factory) {
        return factory.from(ShiftAssignment.class)
                .filter(sa -> sa.getEmployee() != null)
                .join(factory.from(ConstraintConfig.class))
                .filter((sa, cfg) -> 
                    (cfg.isAvoidWorkdayShifts() || 
                     (cfg.isAllowPersonalPreference() && 
                      sa.getEmployee().getPreference().isAvoidWorkday())) && 
                    !sa.getShift().isHoliday()
                )
                .penalize("避免工作日排班", HardSoftScore.ofSoft(1));
    }
    /** ⑨ 软约束:避免排班过于集中 */
    private Constraint avoidClusteredAssignments(ConstraintFactory factory) {
        return factory.from(ShiftAssignment.class)
                .filter(sa -> sa.getEmployee() != null)
                .join(ShiftAssignment.class,
                        Joiners.equal(ShiftAssignment::getEmployee),
                        Joiners.lessThan(sa -> sa.getShift().getDate()))
                .join(factory.from(ConstraintConfig.class))
                .filter((a, b, cfg) -> {
                    long daysBetween = ChronoUnit.DAYS.between(
                        a.getShift().getDate(), 
                        b.getShift().getDate()
                    );
                    return daysBetween > 0 && daysBetween <= 2;
                })
                .penalize("避免排班过于集中", HardSoftScore.ofSoft(5));
    }
}

约束编写最佳实践

1. 命名清晰

// ✅ 好:清楚表达约束含义
.penalize("避免连续排班", HardSoftScore.ofHard(1))
// ❌ 差:模糊不清
.penalize("constraint1", HardSoftScore.ofHard(1))

2. 使用过滤优先

// ✅ 好:先过滤,减少计算
.filter(sa -> sa.getEmployee() != null)
.penalize(...)
// ❌ 差:后续在判断中处理null
.penalize(..., (sa, cfg) -> sa.getEmployee() == null ? 0 : penalty)

3. 平衡评分权重

// 硬约束权重通常设为1(表示不可违反)
HardSoftScore.ofHard(1)
// 软约束权重根据重要性设置
HardSoftScore.ofSoft(1)      // 低优先级
HardSoftScore.ofSoft(100)    // 中等优先级
HardSoftScore.ofSoft(1000)   // 高优先级

实际应用

PlannerController - API接口

@RestController
@RequestMapping("/planner")
public class PlannerController {
    @Resource
    private RosterGenerator rosterGenerator;
    @PostMapping("/solve")
    public Object solve(@RequestBody ConstraintConfig config) {
        // 1. 从数据库加载数据并生成Roster对象
        List<Roster> rosters = rosterGenerator.loadRosterFromDB(config);
        // 2. 使用线程池并行求解多个Roster
        ExecutorService executor = Executors.newFixedThreadPool(5);
        List<Future<Roster>> futures = new ArrayList<>();
        for (Roster roster : rosters) {
            futures.add(executor.submit(() -> {
                // 3. 为每个Roster创建求解器
                SolverFactory<Roster> factory = SolverFactory
                    .createFromXmlResource("./planner/solverConfig.xml");
                Solver<Roster> solver = factory.buildSolver();
                // 4. 执行求解
                return solver.solve(roster);
            }));
        }
        // 5. 收集所有求解结果
        ArrayList<Roster> result = new ArrayList<>();
        for (Future<Roster> f : futures) {
            result.add(f.get());
        }
        executor.shutdown();
        return result;
    }
}

请求示例

curl -X POST http://localhost:8080/planner/solve \
  -H "Content-Type: application/json" \
  -d '{
    "departIds": ["dept_001", "dept_002"],
    "shiftIds": ["shift_001", "shift_002", "shift_003"],
    "startDate": "2025-11-01",
    "endDate": "2025-11-30",
    "avoidConsecutiveShifts": true,
    "avoidConsecutiveNightShifts": true,
    "allowPersonalPreference": true,
    "balanceWorkload": true,
    "avoidWorkdayShifts": false,
    "avoidHoliday": false,
    "hardWeight": 1,
    "softWeight": 1
  }'

响应示例

[
  {
    "constraintConfig": {...},
    "shiftAssignmentList": [
      {
        "shift": {
          "date": "2025-11-01",
          "isHoliday": false,
          "type": "MORNING",
          "isNight": false
        },
        "employee": {
          "id": "emp_001",
          "name": "张三",
          "availableShifts": "shift_001,shift_002"
        }
      }
    ],
    "score": {
      "hardScore": 0,
      "softScore": -450
    }
  }
]

性能优化

1. 求解配置优化

<!-- 延长求解时间以获得更优解 -->
<termination>
    <secondsSpentLimit>30</secondsSpentLimit>
    <bestScoreLimit>0hard/-3000soft</bestScoreLimit>
</termination>
参数含义影响
secondsSpentLimit最长求解时间时间越长解越优,但响应慢
bestScoreLimit目标评分达到目标立即停止
constructionHeuristicType初始解策略影响启发阶段速度
acceptedCountLimit每步评估数越大越精确,越慢

2. 初始解优化

改进 RosterGenerator 中的初始分配策略:

// 当前:随机轮转
int selectedIndex = (i + random.nextInt(employeeCount)) % employeeCount;
// 改进:考虑员工可用班次
int selectedIndex = selectBestEmployee(shifts.get(i), employeeList, assignments);
private int selectBestEmployee(Shift shift, List<Employee> employees, 
                               List<ShiftAssignment> assignments) {
    Employee best = null;
    int minAssignments = Integer.MAX_VALUE;
    for (Employee emp : employees) {
        // 检查员工是否可以上该班次
        if (!canAssignShift(emp, shift)) continue;
        // 选择已分配班次最少的员工
        long count = assignments.stream()
            .filter(sa -> sa.getEmployee().equals(emp))
            .count();
        if (count < minAssignments) {
            minAssignments = (int) count;
            best = emp;
        }
    }
    return best != null ? employees.indexOf(best) : 0;
}

3. 约束优化

// ✅ 好:使用Joiners进行高效连接
.join(ShiftAssignment.class,
        Joiners.equal(ShiftAssignment::getEmployee),
        Joiners.lessThan(sa -> sa.getShift().getDate()))
// ❌ 差:在filter中进行复杂计算
.filter(a -> assignments.stream()
    .filter(b -> b.getEmployee().equals(a.getEmployee()))
    .count() > 1)

4. 数据结构优化

// 预计算班次映射,避免重复查询
Map<String, Integer> shiftMap = shiftList.stream()
    .collect(Collectors.toMap(DutyShift::getId, DutyShift::getOrder));
// 预计算节假日集合,提高查询速度
Set<LocalDate> holidayDates = holidays.stream()
    .map(h -> h.getCalendarDate())
    .collect(Collectors.toSet());

5. 索引优化建议

// 数据库索引建议
CREATE INDEX idx_duty_staff_depart ON duty_staff(depart_id);
CREATE INDEX idx_duty_holiday_date ON duty_holiday(calendar_date);
CREATE INDEX idx_duty_shift_order ON duty_shift(`order`);

常见问题

Q1: 求解没有找到可行解?

症状: 返回 0hard/-XXsoft 的评分

原因分析:

  • 硬约束过多或相互冲突
  • 员工人数过少,无法覆盖所有班次
  • 员工可用班次限制过严

解决方案:

  1. 检查约束配置
// 减少硬约束
avoidConsecutiveShifts(factory),    // 改为软约束
  • 增加员工数量或扩大可用班次
  • 调整初始解策略

Q2: 求解速度太慢?

症状: 求解时间超过预期

优化步骤:

// ①减少数据量
List<Roster> rosters = generator.loadRosterFromDB(config);  // 较大
// ↓
// 改为按部门分批求解
List<Roster> batch1 = generator.loadRosterForDept(config, "dept_001");
List<Roster> batch2 = generator.loadRosterForDept(config, "dept_002");
// ②简化约束
return new Constraint[]{
    shiftMustHaveAssignment(factory),        // 保留必需
    mustBeAvailableForShift(factory),
    // avoidConsecutiveShifts(factory),      // 移除非关键
    // avoidClusteredAssignments(factory),
};
// ③降低求解时间
<secondsSpentLimit>5</secondsSpentLimit>  // 从10秒改为5秒

Q3: 评分计算不合理?

症状: 明显不均衡的排班方案

检查清单:

// 1. 检查权重设置
balanceWorkload(factory)
    .penalize(..., HardSoftScore.ofSoft(1000),  // 权重是否合适?
        (emp, count, cfg) -> cfg.isBalanceWorkload() ? Math.abs(count - 5) : 0);
// 2. 检查目标值设置
Math.abs(count - 5)  // 5是否应该是 count/average?
// 3. 检查约束条件
filter((a, b, cfg) -> cfg.isBalanceWorkload() ? ... : true)
// ↑ 应该返回false,表示不应用该约束

改进方案:

// 改用相对差异
int avgCount = totalShifts / employeeCount;
return Math.abs(count - avgCount);
// 或使用百分位数
double threshold = avgCount * 1.2;  // 允许超过20%
return count > threshold ? count - threshold : 0;

Q4: 内存占用过大?

症状: OutOfMemoryError 或响应缓慢

优化方案:

// ①减少规划实体数量
// 原:month × 3shifts × departments = 大量ShiftAssignment
// 改:预处理,只保留必要的
// ②使用流式处理
List<ShiftAssignment> assignments = new ArrayList<>();
for (Shift shift : shifts) {
    assignments.add(new ShiftAssignment(shift, null));  // 延迟分配
}
// ③分批求解
int batchSize = 100;
for (int i = 0; i < shifts.size(); i += batchSize) {
    List<Shift> batch = shifts.subList(i, Math.min(i + batchSize, shifts.size()));
    solveBatch(batch);
}

Q5: 如何调试约束?

启用详细日志:

logging:
  level:
    org.optaplanner: DEBUG
    org.jeecg.modules.planner: DEBUG

在约束中添加日志:

private Constraint avoidConsecutiveShifts(ConstraintFactory factory) {
    return factory.from(ShiftAssignment.class)
            .filter(sa -> sa.getEmployee() != null)
            .join(ShiftAssignment.class,
                    Joiners.equal(ShiftAssignment::getEmployee),
                    Joiners.lessThan(sa -> sa.getShift().getDate()))
            .join(factory.from(ConstraintConfig.class))
            .filter((a, b, cfg) -> {
                boolean violated = /* violation check */;
                if (violated) {
                    log.debug("连续排班违反: emp={}, date1={}, date2={}",
                        a.getEmployee().getName(),
                        a.getShift().getDate(),
                        b.getShift().getDate());
                }
                return violated;
            })
            .penalize("避免连续排班", HardSoftScore.ofHard(1));
}

总结

核心要点回顾

方面要点
集成添加依赖 → 创建配置文件 → 定义域模型 → 实现约束
设计PlanningSolution + PlanningEntity + ConstraintProvider
优化平衡求解时间和解质量,合理设置权重
调试启用日志,检查约束违反情况,验证评分计算

最佳实践清单

  • ✅ 将硬约束权重设为1,软约束根据优先级调整
  • ✅ 在filter中尽早过滤null值和不符合条件的数据
  • ✅ 使用Joiners进行高效的连接操作
  • ✅ 为约束提供清晰的名称便于调试
  • ✅ 使用并行求解加快处理速度
  • ✅ 定期监控求解时间和解质量
  • ✅ 为初始解提供更好的启发式策略

进一步学习资源

到此这篇关于SpringBoot集成OptaPlanner与使用指南的文章就介绍到这了,更多相关SpringBoot OptaPlanner使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java中一些基础概念的使用详解

    Java中一些基础概念的使用详解

    本篇文章是对在Java中一些基础概念的使用进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • 使用JPA自定义SQL查询结果

    使用JPA自定义SQL查询结果

    这篇文章主要介绍了使用JPA自定义SQL查询结果,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • Java中字符串的一些常见方法分享

    Java中字符串的一些常见方法分享

    这篇文章主要介绍了Java中字符串的一些常见方法,需要的朋友可以参考下
    2014-02-02
  • springboot项目中使用docker进行远程部署的实现

    springboot项目中使用docker进行远程部署的实现

    本文主要介绍了在Spring Boot项目中使用Docker进行远程部署,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-01-01
  • 使用SpringBoot自定义starter详解

    使用SpringBoot自定义starter详解

    这篇文章主要介绍了使用Spring Boot自定义starter详解,文中有非常详细的代码示例,对正在学习java的小伙伴们有很好地帮助哟,需要的朋友可以参考下
    2021-05-05
  • Spring Data Neo4j实现复杂查询的多种方式

    Spring Data Neo4j实现复杂查询的多种方式

    在 Spring Data Neo4j 中,实现复杂查询可以通过多种方式进行,包括使用自定义查询、方法命名查询以及使用 Cypher 查询语言,以下是详细介绍,帮助你在 Spring Data Neo4j 中实现复杂查询,需要的朋友可以参考下
    2024-11-11
  • futuretask用法及使用场景介绍

    futuretask用法及使用场景介绍

    这篇文章主要介绍了futuretask用法及使用场景介绍,小编觉得挺不错的,这里分享给大家,供大家参考。
    2017-10-10
  • 深入理解Java设计模式之桥接模式

    深入理解Java设计模式之桥接模式

    这篇文章主要介绍了JAVA设计模式之桥接模式的的相关资料,文中示例代码非常详细,供大家参考和学习,感兴趣的朋友可以了解下
    2021-11-11
  • java实现md5加密示例

    java实现md5加密示例

    这篇文章主要介绍了java实现md5加密示例,需要的朋友可以参考下
    2014-05-05
  • 基于JavaMail的Java邮件发送

    基于JavaMail的Java邮件发送

    电子邮件的应用非常广泛,例如在某网站注册了一个账户,自动发送一封欢迎邮件,通过邮件找回密码,自动批量发送活动信息等。本文将简单介绍如何通过 Java 代码来创建电子邮件,并连接邮件服务器发送邮件
    2021-10-10

最新评论