Optaplanner solutionClass entityCollectionProperty should never return null error when simple JSON object passed to controller

Optaplanner solutionClass entityCollectionProperty should never return null error when simple JSON object passed to controller

我正在处理 optaplanner-spring-boot-starter 云平衡项目,我正在尝试根据员工的技能水平为他们分配班次。但是,当我将 JSON 对象传递给我的花名册控制器时,我收到一条错误消息:

java.lang.IllegalArgumentException:solutionClass (class com.redhat.optaplannersbs.domain.Roster) 的 entityCollectionProperty (bean 属性 shiftList on class com.redhat.optaplannersbs.domain.Roster) 永远不应该return 空。

我不明白问题是什么,因为我基本上在做与云平衡问题相同的事情,并且运行并解决得很好。

这是我的员工代码 class:

public class Employee {
    private int eid;

    private String name;

    private int skillLevel;

    public Employee(){

    }

    public int getEid() {
        return eid;
    }

    public void setEid(int eid) {
        this.eid = eid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    // constraint getters and setters
    public int getSkillLevel() {
        return skillLevel;
    }

    public void setSkillLevel(int skillLevel) {
        this.skillLevel = skillLevel;
    }


}

这是我的班次代码 class:

 @PlanningEntity
public class Shift {
    private int sid;

    private LocalTime startTime;

    private LocalTime endTime;

    private int requiredSkillLevel;

    @PlanningVariable(valueRangeProviderRefs = "employee")
    private Employee employee;

    public Shift(){

    }

    public Shift(Long deptId, Long spotId, LocalTime startTime,
                 LocalTime endTime, Long employeeId){
        this.startTime = startTime;
        this.endTime = endTime;
    }

    public int getSid() {
        return sid;
    }

    public void setSid(int sid) {
        this.sid = sid;
    }

    public LocalTime getStartTime() {
        return startTime;
    }

    public void setStartTime(LocalTime startTime) {
        this.startTime = startTime;
    }

    public LocalTime getEndTime() {
        return endTime;
    }

    public void setEndTime(LocalTime endTime) {
        this.endTime = endTime;
    }


    // planning variable getter and setter

    public Employee getEmployee() {
        return employee;
    }

    public void setEmployee(Employee employee) {
        this.employee = employee;
    }

    public int getRequiredSkillLevel() {
        return requiredSkillLevel;
    }

    public void setRequiredSkillLevel(int requiredSkillLevel) {
        this.requiredSkillLevel = requiredSkillLevel;
    }
}

这是我的花名册class:

 @PlanningSolution
public class Roster {

    private List<Employee> employeeList;

    private List<Shift> shiftList;

    private HardSoftScore score;

    public Roster(List<Employee> employeeList, List<Shift> shiftList) {
        this.employeeList = employeeList;
        this.shiftList = shiftList;
    }

    @ProblemFactCollectionProperty
    @ValueRangeProvider(id="employee")
    public List<Employee> getEmployeeList() {
        return employeeList;
    }

    public void setEmployeeList(List<Employee> employeeList) {
        this.employeeList = employeeList;
    }

    @PlanningEntityCollectionProperty
    public List<Shift> getShiftList() {
        return shiftList;
    }

    public void setShiftList(List<Shift> shiftList) {
        this.shiftList = shiftList;
    }

    @PlanningScore
    public HardSoftScore getScore() {
        return score;
    }

    public void setScore(HardSoftScore score) {
        this.score = score;
    }
}

这是我的约束提供者:

    public class ConstraintProvider implements org.optaplanner.core.api.score.stream.ConstraintProvider {
    @Override
    public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
        return new Constraint[]{
                requiredSkillLevelOfEmployeesForShifts(constraintFactory)
        };
    }

    private Constraint requiredSkillLevelOfEmployeesForShifts(ConstraintFactory constraintFactory) {
        return constraintFactory.from(Shift.class)
                .groupBy(Shift::getEmployee, sum(Shift::getRequiredSkillLevel))
                .filter((employee, requiredSkillLevel) -> requiredSkillLevel > employee.getSkillLevel())
                .penalize("requiredSkillLevelForShifts",
                HardSoftScore.ONE_HARD,
                (employee, requiredSkillLevel) -> requiredSkillLevel - employee.getSkillLevel());
    }
}

这是我的控制器:

@RestController
@RequestMapping("/roster")
public class RosterController {

    @Autowired
    private SolverManager<Roster, UUID> solverManager;

    @PostMapping("/solve")
    public Roster solve(@RequestBody Roster problem) {
        UUID problemId = UUID.randomUUID();
        // Submit the problem to start solving
        SolverJob<Roster, UUID> solverJob = solverManager.solve(problemId, problem);
        Roster solution;
        try {
            // Wait until the solving ends
            solution = solverJob.getFinalBestSolution();
        } catch (InterruptedException | ExecutionException e) {
            throw new IllegalStateException("Solving failed.", e);
        }
        return solution;
    }

}

我通过post请求传递的JSON数据如下:

 {
   "shifts":[
      {
         "sid":0,
         "startTime":"09:00",
         "endTime":"18:00",
         "requiredSkillLevel": 12
      },
      {
         "sid":1,
         "startTime":"12:00",
         "endTime":"20:00",
         "requiredSkillLevel": 10
      },
      {
         "sid":2,
         "startTime":"18:00",
         "endTime":"00:00",
         "requiredSkillLevel": 10
      },
      {
         "sid": 3,
         "startTime":"09:00",
         "endTime":"18:00",
         "requiredSkillLevel": 12
      },
      {
         "sid":4,
         "startTime":"12:00",
         "endTime":"20:00",
         "requiredSkillLevel": 10
      },
      {
         "sid":5,
         "startTime":"18:00",
         "endTime":"00:00",
         "requiredSkillLevel":10
      },
      {
         "sid":6,
         "startTime":"09:00",
         "endTime":"18:00",
         "requiredSkillLevel": 12
      },
      {
         "sid":7,
         "startTime":"12:00",
         "endTime":"20:00",
         "requiredSkillLevel": 10
      },
      {
         "sid":8,
         "startTime":"18:00",
         "endTime":"00:00",
         "requiredSkillLevel":10
      },
      {
         "sid":9,
         "startTime":"09:00",
         "endTime":"18:00",
         "requiredSkillLevel": 12
      },
      {
         "sid":10,
         "startTime":"12:00",
         "endTime":"20:00",
         "requiredSkillLevel": 10
      },
      {
         "sid":11,
         "startTime":"18:00",
         "endTime":"00:00",
         "requiredSkillLevel":10
      },
      {
         "sid":12,
         "startTime":"09:00",
         "endTime":"18:00",
         "requiredSkillLevel": 12
      },
      {
         "sid":13,
         "startTime":"12:00",
         "endTime":"20:00",
         "requiredSkillLevel": 10
      },
      {
         "sid":14,
         "startTime":"18:00",
         "endTime":"00:00",
         "requiredSkillLevel":10
      }
   ],
   "employees":[
      {
         "eid":0,
         "name":"john",
         "skillLevel": 10
      },
      {
         "eid":1,
         "name":"elaine",
         "skillLevel": 2
      },
      {
         "eid":2,
         "name":"kieran",
         "skillLevel": 11
      },
      {
         "eid":3,
         "name":"maeve",
         "skillLevel": 10
      },
      {
         "eid":4,
         "name":"steve",
         "skillLevel": 9
      },
      {
         "eid":5,
         "name":"steve",
         "skillLevel": 9
      },
      {
         "eid":6,
         "name":"steve",
         "skillLevel": 15
      },
      {
         "eid":7,
         "name":"amy",
         "skillLevel": 11
      }
   ]
}

我不应该得到这个错误,因为我正在做一个更简单的云平衡应用程序版本,如果有人能找出我哪里出错了,那将是一个很大的帮助

在您调用 solverManager.solve(...) 之前,在您的 RosterController 中放置一个断点。您会看到您的 Roster 实例有一个 shiftList 字段,即 null.

问题出在输入数据的 json 解组中,因为输入中的命名不匹配 json。请注意,默认情况下 Spring Boot 中的 Jackson 会忽略不存在的属性,而不是 fail-fasting(我从未理解的设计决策)。有一个 属性 可以改变这种行为 IIRC。