본문 바로가기
Back/JPA

[JPA] 이전글, 다음글 조회(native query, QueryDSL 사용)

by 은z 2023. 12. 19.

상황

게시판 구현을 하다보면 상세페이지에서 이전글, 다음글 바로가기를 쉽게 볼 수 있다.
MSSQL, Oracle이 DB 벤더이면, 이전 행의 값을 찾거나 다음 행의 값을 찾기 위해서 사용하는 LAG, LEAD 함수로 쉽게 구현이 가능하다.

하지만 현재 JPA를 이용하여 이전글, 다음글을 구현해야 하는 상황인데 JPA는 LAG, LEAD와 같은 함수를 지원하지 않는다.
이전글, 다음글을 구현하는 방법 중 내가 찾은 방법은 두가지!
그리고 각각의 방법이 지닌 한계도 있으니 참고하여 상황에 맞는 방법을 선택하면 좋을 것 같다.


적용

 

✔️1. Native Query로 구현하기

📌설명: Native Query 사용 시, Entity가 아닌 Return값을 반환받기 위해서는 Interface based Projection 을 활용해야 한다. 즉, 반환받을 DTO class가 아닌 Interface로 생성해야 필드의 매핑이 오류없이 된다.

 

📌한계 : lead,lag 함수를 지원하는 MSSQL이나 Oracle에 DB가 종속되어 버린다.
예컨대 만약 사용하는 DB벤더가 MySQL이면 이 함수로 쿼리를 날릴 수가 없다.(당연히 지원하지 않기 때문에 조회불가)

 

 

이제 코드를 살펴보자.

 

⬇️SampleBoardRepository.java

@Query(value = " select prevNo, prevTitle, nextNo, nextTitle " +
                    " from ( " + 
                    "   select " +
                    "       no " +
                    "       , lag(no, 1, null) over(order by post_dt desc, no desc) as nextNo " +
                    "       , lag(title, 1, null) over(order by post_dt desc, no desc) as nextTitle " +
                    "       , lead(no, 1, null) over(order by post_dt desc, no desc) as prevNo " +
                    "       , lead(title, 1, null) over(order by post_dt desc, no desc) as prevTitle " +
                    "   from " +
                    "       SAMPLE_BOARD " +
                    "   where " +
                    "       display_yn = 'Y') A" +
                    " where no = :no "
            , nativeQuery = true)
PrevNextResponse findPrevNext(@Param("no") Long no);

✏️ @Query 어노테이션을 달고, nativeQuery = true 값을 준 뒤에, value에 문자열 형태로 쿼리문을 작성하면 된다.

 

 

⬇️PrevNextResponse.java

/**
 * 이전, 다음글 정보 Response DTO interface
 * interface로 정의한 이유 : native query로 DTO기반의 projection 사용 시 필드를 Mapping하기 위해
 */
public interface PrevNextResponse {

    Long getPrevNo();

    String getPrevTitle();

    Long getNextNo();

    String getNextTitle();

}

✏️ 위에서 설명한대로, 엔티티가 아닌 DTO로 반환을 하기 위해서는 interface로 정의하여 리턴 받아야한다.

 

 

✔️2. QueryDSL 로 구현하기

📌 설명 : 조건에 해당하는 데이터를 정제하여 정렬 후에 이전, 다음 각각 한 개의 행만 가져와서 반환하는 방법이다. 이것은 특정 DB벤더에 종속되지 않으므로 JPA를 사용하는 목적과 부합한다.

 

📌 한계 : 실제 플젝을 진행하다 겪게된 문제인데, orderBy의 기준이 되는 컬럼이 두 개 이상이면 원하는 결과값을 가져오지 않을 수 있다.
코드를 보면 postDt(작성일), no(일련번호) 이렇게 두 가지가 정렬기준이다.
만약 postDt가 일치하면 lt(<) gt(>) 조건식에서 아예 제거되어 버리기 때문이다.  (자세한건.. 쿼리를 직접 날려보며 확인하길..)
서브쿼리를 이용하여 쿼리를 만들면 처리가 되긴 하는데, 우리는 팀원과 협의를 통해서 Native Query 방식을 사용하기로 했다.

만약 정렬기준에 중복이 없다면 natvie Query 보다 아래 코드가  더욱 적합할 듯 하다.

 

 

⬇️SampleBoardRepositoryCustomImpl.java

/**
     * 이전글(과거)
     */
    @Override
    public SampleBoard findPrevBoard(Long no, LocalDateTime postDt) {
        return jpaQueryFactory.select(
                        Projections.constructor(SampleBoard.class,
                                sampleBoard.no,
                                sampleBoard.title
                        )
                )
                .from(sampleBoard)
                .where(
                        sampleBoard.displayYn.eq(YnType.YES)
                        , sampleBoard.postDt.lt(postDt)
                )
                .orderBy(sampleBoard.postDt.desc(), sampleBoard.no.desc())
                .limit(1)
                .fetchOne();
    }

    /**
     * 다음글(미래)
     */
    @Override
    public SampleBoard findNextBoard(Long no, LocalDateTime postDt) {
        return jpaQueryFactory.select(
                        Projections.constructor(SampleBoard.class,
                                sampleBoard.no,
                                sampleBoard.title
                        )
                )
                .from(sampleBoard)
                .where(
                        sampleBoard.displayYn.eq(YnType.YES)
                        , sampleBoard.postDt.gt(postDt)
                )
                .orderBy(sampleBoard.postDt.asc(), sampleBoard.no.asc())
                .limit(1)
                .fetchOne();
    }

 

댓글