第28章 データベースと UI 統合の追加
Spring Boot を使用して Red Hat Business Optimizer アプリケーションの例を作成したら、データベースと UI 統合を追加します。
前提条件/事前作業
- Red Hat Business Optimizer Spring Boot の時間割サンプルが作成されている。
手順
-
Timeslot
、Room
、およびLesson
の Java Persistence API (JPA) リポジトリーを作成します。JPA リポジトリーの作成に関する情報は、Spring の Web サイトの Accessing Data with JPA を参照してください。 - REST で JPA リポジトリーを公開します。リポジトリーの公開に関する情報は、Spring の Web サイトの Accessing JPA Data with REST を参照してください。
-
TimeTableRepository
Facade をビルドして、1 回のトランザクションで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); } }
簡素化するために、このコードは
TimeTable
インスタンスを 1 つだけ処理しますが、マルチテナントを有効にして、異なる高校のTimeTable
インスタンスを複数、平行して処理することも簡単にできます。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 machines and doesn't randomly fail on slow machines. 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 を構築します。