SpringBoot集成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 的评分
原因分析:
- 硬约束过多或相互冲突
- 员工人数过少,无法覆盖所有班次
- 员工可用班次限制过严
解决方案:
- 检查约束配置
// 减少硬约束 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使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
springboot项目中使用docker进行远程部署的实现
本文主要介绍了在Spring Boot项目中使用Docker进行远程部署,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧2025-01-01


最新评论