우선 홈페이지의 seminar 도메인을 설명하겠습니다.
현재 키퍼 동아리 회원은 활동회원이 거의 60명 가까이 됩니다.
매주 혹은 2주에 한번 씩 진행하는 세미나에서 회원 출석 체크를 서기가 일일히 하는 수고를 덜기 위한 자동 출석 서비스를 제공하기 위해 만들어진 도메인 입니다.
세미나 도메인의 테이블은 다음의 4개가 있습니다.
이번 글에서는 seminar 테이블을 소개하기로 하고, 나머지 테이블들은 기회가 될 때 소개하도록 하겠습니다.

seminar 테이블에는 세미나의 정보를 담기 위한 8개의 column이 있습니다. 그 중 아래 3개의 컬럼을 주목합시다.
각 컬럼이 어떤 용도인 지 한눈에 파악이 가능한 것 같습니다.
홈페이지 리뉴얼 프로젝트에서는 기획자가 있고, 각 페이지를 어떻게 구성할 것인 지 정하는 업무를 담당하고 있습니다. 그래서 기획이 끝나면 프론트/백엔드 개발 담당은 기획서를 보면서 기능을 구현하면 됩니다.

기획서를 보니, 3가지 종류의 세미나를 조회해야 합니다.

상세 설명을 보니 다음의 3가지 세미나를 조회해야 한다고 적혀있습니다.
그냥 슥 봐도 뭔가 복잡한 쿼리문을 날려야 할 것 같습니다. 그보다 중요한 건 어디에 기준을 두느냐에 따라서 조회 결과가 달라질 수도 있기에 요구사항을 확실하게 짚고 넘어갈 필요가 있었습니다. 사실 이 글도 이 포인트에서 복잡한 생각을 정리해보고자 작성하게 되었습니다.
처음 백엔드 개발에 참여했을 때도, 이와 비슷한 기능 구현 요청이 있었는데 헷갈리고 어려워서 혼자 해결하지 못했었던 경험이 있었습니다 😅
예정된 세미나 중 2번째 가까운 세미나가 왜 필요한 지 처음에 의아했습니다. 기획자님께 관련해서 질문을 드렸고, 아래와 같은 답변을 받았습니다.
세미나 일정 페이지를 만들게 되면 앞으로의 세미나에 대한 정보가 일정에 등록해두기 위해 미리 앞으로 진행될 세미나에 대해서 추가해두는 상황을 염두해둔 것
바로 이해 할 수 있었습니다 👍
끝난 세미나는 lateness_close_time (지각 마감 시간)이 현재보다 이전인 세미나 입니다.
예정된 세미나는 open_time (세미나 시작 시간)이 현재보다 이후인 세미나 입니다.
그래서 저는 다음 그림처럼 기능을 구현할 생각이였습니다.

그런데 뭔가 헷갈려서 이야기를 해 본 결과, 기획자님은 다음처럼 조회되길 생각하셨습니다.

즉, 오늘의 세미나가 마감 되었더라도 이를 포함하여 예정된 세미나를 불러와야 합니다.
3개의 api를 호출해야 할 텐데, 한번에 3개의 세미나 정보를 던져주는 게 좋을 지 각각을 분리하는 게 좋을 지도 고민했습니다.
일단 오버헤드가 크지 않다면 각각의 api로 분리하는 게 좋다고 판단하였습니다.
@Query("SELECT s FROM Seminar s "
+ "WHERE s.id <> 1 " // virtual seminar data 제외
+ "AND DATE(s.openTime) < :localDate "
+ "AND DATE(s.latenessCloseTime) < :localDate "
+ "ORDER BY s.openTime DESC "
+ "LIMIT 1")
Optional<Seminar> findRecentlyDoneSeminar(@Param("localDate") LocalDate localDate);
저는 프로젝트에서 복잡한 조회를 해야 할 때는 거의JPQL로 쿼리를 작성하였습니다. JPA에서 제공하는 메서드보다는 사용할 때 가독성이 좋지만, 쿼리를 작성할 때 주의하여 작성해야 해서 (이게 생각보다 부담입니다.) 좋은 방법인지 고민입니다. 테스트도 거의 쿼리문에 대한 테스트를 작성한 것 같습니다.
그래서 Query DSL을 써보는 것도 좋을 것 같네요. 일단은 부채로 남겨 두겠습니다.
Query DSL 장점
@Query("SELECT s FROM Seminar s "
+ "WHERE s.id <> 1 " // virtual seminar data 제외"
+ "AND DATE(s.openTime) >= :localDate "
+ "ORDER BY s.openTime ASC "
+ "LIMIT 2")
List<Seminar> findRecentlyUpcomingSeminar(@Param("localDate") LocalDate localDate);
오늘을 포함해야 하므로 >= 으로 openTime을 비교해줍니다. 또 이번에는 openTime을 오름차순으로 정렬했을 때 최상위 2개의 세미나를 조회해야 합니다.