12.8. 添加数据库和 UI 集成
在使用 Spring Boot 创建红帽构建的 OptaPlanner 应用程序示例后,添加数据库和 UI 集成。
前提条件
- 您已创建了 OptaPlanner Spring Boot timetable 示例。
流程
-
为
Timeslot
、Room
和Lesson
创建 Java Persistence API(JPA)存储库。有关创建 JPA 软件仓库的详情,请参考 Spring 网站上 使用 JPA 访问数据。 - 通过 REST 公开 JPA 存储库。有关公开软件仓库的详情,请参阅 Spring 网站上 使用 REST 访问 JPA 数据。
-
构建一个可
时间的Repository
常见问题解答,以在单个事务中读取和写入一个TimeTable
。 调整
TimeTableController
,如下例所示:package com.example.solver; import com.example.domain.TimeTable; import com.example.persistence.TimeTableRepository; import org.optaplanner.core.api.score.ScoreManager; import org.optaplanner.core.api.solver.SolverManager; import org.optaplanner.core.api.solver.SolverStatus; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/timeTable") public class TimeTableController { @Autowired private TimeTableRepository timeTableRepository; @Autowired private SolverManager<TimeTable, Long> solverManager; @Autowired private ScoreManager<TimeTable> scoreManager; // To try, GET http://localhost:8080/timeTable @GetMapping() public TimeTable getTimeTable() { // Get the solver status before loading the solution // to avoid the race condition that the solver terminates between them SolverStatus solverStatus = getSolverStatus(); TimeTable solution = timeTableRepository.findById(TimeTableRepository.SINGLETON_TIME_TABLE_ID); scoreManager.updateScore(solution); // Sets the score solution.setSolverStatus(solverStatus); return solution; } @PostMapping("/solve") public void solve() { solverManager.solveAndListen(TimeTableRepository.SINGLETON_TIME_TABLE_ID, timeTableRepository::findById, timeTableRepository::save); } public SolverStatus getSolverStatus() { return solverManager.getSolverStatus(TimeTableRepository.SINGLETON_TIME_TABLE_ID); } @PostMapping("/stopSolving") public void stopSolving() { solverManager.terminateEarly(TimeTableRepository.SINGLETON_TIME_TABLE_ID); } }
为简单起见,此代码只处理一个
可维护的
实例,但要直接启用多租户,并并行处理不同高中中不同中级的多个可时间
实例。getTimeTable()
方法返回来自数据库的最新时间表。它使用ScoreManager
(自动注入)来计算该时间表的分数,以便 UI 可以显示分数。solve()
方法启动一个作业,以解决当前可时间表,并将时间窗和房分配存储在数据库中。它使用SolverManager.solveAndListen()
方法侦听中间最佳解决方案,并相应地更新数据库。这可让 UI 显示后端仍在解决过程中的进度。现在,
solve()
方法会立即返回,请调整TimeTableControllerTest
,如下例所示:package com.example.solver; import com.example.domain.Lesson; import com.example.domain.TimeTable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.optaplanner.core.api.solver.SolverStatus; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @SpringBootTest(properties = { "optaplanner.solver.termination.spent-limit=1h", // Effectively disable this termination in favor of the best-score-limit "optaplanner.solver.termination.best-score-limit=0hard/*soft"}) public class TimeTableControllerTest { @Autowired private TimeTableController timeTableController; @Test @Timeout(600_000) public void solveDemoDataUntilFeasible() throws InterruptedException { timeTableController.solve(); TimeTable timeTable = timeTableController.getTimeTable(); while (timeTable.getSolverStatus() != SolverStatus.NOT_SOLVING) { // Quick polling (not a Test Thread Sleep anti-pattern) // Test is still fast on fast systems and doesn't randomly fail on slow systems. Thread.sleep(20L); timeTable = timeTableController.getTimeTable(); } assertFalse(timeTable.getLessonList().isEmpty()); for (Lesson lesson : timeTable.getLessonList()) { assertNotNull(lesson.getTimeslot()); assertNotNull(lesson.getRoom()); } assertTrue(timeTable.getScore().isFeasible()); } }
- 轮询最新的解决方案,直到解决解决为止。
- 为了视觉化呈现时间,请在这些 REST 方法之上构建具有吸引力的 Web UI。