14.9. 将数据库与您的 Quarkus OptaPlanner research timetable 应用程序集成
创建 Quarkus OptaPlanner PROFILE timetable 应用程序后,您可以将其与数据库集成,并创建基于 Web 的用户界面来显示可时间性。
先决条件
- 您有一个 Quarkus OptaPlanner school timetable 应用程序。
流程
-
使用 Hibernate 和 Panache 将
Timeslot
、Room
和 lesson 实例存储在数据库中。如需更多信息,请参阅使用 Panache 简化 Hibernate ORM。
- 通过 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()); } } }
本例包含一个
TimeTable
实例。但是,您可以为并行多个 ultra 启用多租户和处理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()); } }
- 在这些 REST 方法之上构建 Web UI,以提供可时间的可视化表示。
- 查看 快速入门源代码。