10.10. 将数据库与您的 Quarkus OptaPlanner 64)程序集成
创建 Quarkus OptaPlanner respectively Timetable 应用程序后,您可以将它与数据库集成,并创建一个基于 web 的用户界面来显示可的时间。
先决条件
- 您有一个 Quarkus OptaPlanner school Timetable 应用程序。
流程
-
使用 Hibernate 和 Panache 将
时间、Room和Lesson实例存储在数据库中。如需更多信息,请参阅 Simplified Hibernate ORM with Panache。 - 通过 REST 公开实例。如需更多信息,请参阅 编写 JSON REST 服务。
更新
TimeTableResource类,以在单个事务中读取和写入TimeTable实例:package org.acme.optaplanner.rest; import javax.inject.Inject; import javax.transaction.Transactional; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import io.quarkus.panache.common.Sort; import org.acme.optaplanner.domain.Lesson; import org.acme.optaplanner.domain.Room; import org.acme.optaplanner.domain.TimeTable; import org.acme.optaplanner.domain.Timeslot; import org.optaplanner.core.api.score.ScoreManager; import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore; import org.optaplanner.core.api.solver.SolverManager; import org.optaplanner.core.api.solver.SolverStatus; @Path("/timeTable") public class TimeTableResource { public static final Long SINGLETON_TIME_TABLE_ID = 1L; @Inject SolverManager<TimeTable, Long> solverManager; @Inject ScoreManager<TimeTable, HardSoftScore> scoreManager; // To try, open http://localhost:8080/timeTable @GET 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 = findById(SINGLETON_TIME_TABLE_ID); scoreManager.updateScore(solution); // Sets the score solution.setSolverStatus(solverStatus); return solution; } @POST @Path("/solve") public void solve() { solverManager.solveAndListen(SINGLETON_TIME_TABLE_ID, this::findById, this::save); } public SolverStatus getSolverStatus() { return solverManager.getSolverStatus(SINGLETON_TIME_TABLE_ID); } @POST @Path("/stopSolving") public void stopSolving() { solverManager.terminateEarly(SINGLETON_TIME_TABLE_ID); } @Transactional protected TimeTable findById(Long id) { if (!SINGLETON_TIME_TABLE_ID.equals(id)) { throw new IllegalStateException("There is no timeTable with id (" + id + ")."); } // Occurs in a single transaction, so each initialized lesson references the same timeslot/room instance // that is contained by the timeTable's timeslotList/roomList. return new TimeTable( Timeslot.listAll(Sort.by("dayOfWeek").and("startTime").and("endTime").and("id")), Room.listAll(Sort.by("name").and("id")), Lesson.listAll(Sort.by("subject").and("teacher").and("studentGroup").and("id"))); } @Transactional protected void save(TimeTable timeTable) { for (Lesson lesson : timeTable.getLessonList()) { // TODO this is awfully naive: optimistic locking causes issues if called by the SolverManager Lesson attachedLesson = Lesson.findById(lesson.getId()); attachedLesson.setTimeslot(lesson.getTimeslot()); attachedLesson.setRoom(lesson.getRoom()); } } }package org.acme.optaplanner.rest; import javax.inject.Inject; import javax.transaction.Transactional; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import io.quarkus.panache.common.Sort; import org.acme.optaplanner.domain.Lesson; import org.acme.optaplanner.domain.Room; import org.acme.optaplanner.domain.TimeTable; import org.acme.optaplanner.domain.Timeslot; import org.optaplanner.core.api.score.ScoreManager; import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore; import org.optaplanner.core.api.solver.SolverManager; import org.optaplanner.core.api.solver.SolverStatus; @Path("/timeTable") public class TimeTableResource { public static final Long SINGLETON_TIME_TABLE_ID = 1L; @Inject SolverManager<TimeTable, Long> solverManager; @Inject ScoreManager<TimeTable, HardSoftScore> scoreManager; // To try, open http://localhost:8080/timeTable @GET 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 = findById(SINGLETON_TIME_TABLE_ID); scoreManager.updateScore(solution); // Sets the score solution.setSolverStatus(solverStatus); return solution; } @POST @Path("/solve") public void solve() { solverManager.solveAndListen(SINGLETON_TIME_TABLE_ID, this::findById, this::save); } public SolverStatus getSolverStatus() { return solverManager.getSolverStatus(SINGLETON_TIME_TABLE_ID); } @POST @Path("/stopSolving") public void stopSolving() { solverManager.terminateEarly(SINGLETON_TIME_TABLE_ID); } @Transactional protected TimeTable findById(Long id) { if (!SINGLETON_TIME_TABLE_ID.equals(id)) { throw new IllegalStateException("There is no timeTable with id (" + id + ")."); } // Occurs in a single transaction, so each initialized lesson references the same timeslot/room instance // that is contained by the timeTable's timeslotList/roomList. return new TimeTable( Timeslot.listAll(Sort.by("dayOfWeek").and("startTime").and("endTime").and("id")), Room.listAll(Sort.by("name").and("id")), Lesson.listAll(Sort.by("subject").and("teacher").and("studentGroup").and("id"))); } @Transactional protected void save(TimeTable timeTable) { for (Lesson lesson : timeTable.getLessonList()) { // TODO this is awfully naive: optimistic locking causes issues if called by the SolverManager Lesson attachedLesson = Lesson.findById(lesson.getId()); attachedLesson.setTimeslot(lesson.getTimeslot()); attachedLesson.setRoom(lesson.getRoom()); } } }Copy to Clipboard Copied! Toggle word wrap Toggle overflow 这个示例包含一个
TimeTable实例。但是,您可以为并行的多个校园启用多租户并处理TimeTable实例。getTimeTable ()方法返回数据库的最新时间表。它使用ScoreManager方法(自动注入)来计算该时间表的分数,并使其可用于 UI。solve ()方法启动一个作业,以解决当前的时间表并将时间插槽和房间分配存储在数据库中。它使用SolverManager.solveAndListen ()方法侦听中间最佳解决方案并相应地更新数据库。UI 使用它来显示后端仍在解决时的进度。更新
TimeTableResourceTest类,以反映solve ()方法立即返回,并轮询最新的解决方案,直到解决者完成解决问题:package org.acme.optaplanner.rest; import javax.inject.Inject; import io.quarkus.test.junit.QuarkusTest; import org.acme.optaplanner.domain.Lesson; import org.acme.optaplanner.domain.TimeTable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.optaplanner.core.api.solver.SolverStatus; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @QuarkusTest public class TimeTableResourceTest { @Inject TimeTableResource timeTableResource; @Test @Timeout(600_000) public void solveDemoDataUntilFeasible() throws InterruptedException { timeTableResource.solve(); TimeTable timeTable = timeTableResource.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 = timeTableResource.getTimeTable(); } assertFalse(timeTable.getLessonList().isEmpty()); for (Lesson lesson : timeTable.getLessonList()) { assertNotNull(lesson.getTimeslot()); assertNotNull(lesson.getRoom()); } assertTrue(timeTable.getScore().isFeasible()); } }package org.acme.optaplanner.rest; import javax.inject.Inject; import io.quarkus.test.junit.QuarkusTest; import org.acme.optaplanner.domain.Lesson; import org.acme.optaplanner.domain.TimeTable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.optaplanner.core.api.solver.SolverStatus; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @QuarkusTest public class TimeTableResourceTest { @Inject TimeTableResource timeTableResource; @Test @Timeout(600_000) public void solveDemoDataUntilFeasible() throws InterruptedException { timeTableResource.solve(); TimeTable timeTable = timeTableResource.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 = timeTableResource.getTimeTable(); } assertFalse(timeTable.getLessonList().isEmpty()); for (Lesson lesson : timeTable.getLessonList()) { assertNotNull(lesson.getTimeslot()); assertNotNull(lesson.getRoom()); } assertTrue(timeTable.getScore().isFeasible()); } }Copy to Clipboard Copied! Toggle word wrap Toggle overflow - 在这些 REST 方法基础上构建 Web UI,以提供可时间表的可视化表示。
- 查看 快速入门源代码。