12.8. 添加数据库和 UI 集成

在使用 Spring Boot 创建 OptaPlanner 应用程序示例的红帽构建后,添加数据库和 UI 集成。

前提条件

  • 您已创建了 OptaPlanner Spring Boot Timetable 示例。

流程

  1. TimeslotRoomLesson 创建 Java Persistence API (permanent)存储库。有关创建 JPA 存储库的详情,请参考 Spring 网站 通过 JPA 访问数据。
  2. 通过 REST 公开 JPA 存储库。有关公开存储库的详情,请参考 Spring 网站 通过 REST 访问 JPA 数据。
  3. 构建一个 TimeTableRepository facade,在单个事务中读取和写入一个 TimeTable
  4. 如以下示例所示调整 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);
        }
    
    }

    为了简单起见,这个代码只处理一个 TimeTable 实例,但启用多租户并并行处理不同高校的多个时间表实例是简单的。

    getTimeTable () 方法返回数据库的最新时间表。它使用 ScoreManager (自动注入)计算该时间表的分数,以便 UI 显示分数。

    solve () 方法启动一个作业,从而解决当前的时间表,并将时间插槽和空间分配存储在数据库中。它使用 SolverManager.solveAndListen () 方法侦听中间最佳解决方案并相应地更新数据库。这可让 UI 在后端仍在解决时显示进度。

  5. 现在,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());
        }
    
    }
  6. 轮询最新的解决方案,直到解决者完成解决为止。
  7. 要视觉化时间表,请在这些 REST 方法之上构建有吸引力的 Web UI。