💥 기존의 버전 테이블을 탐색하는 쿼리
현재 프로젝트에서는 버전을 관리하는 테이블이 따로 존재하기 때문에 가장 나중에 업데이트 된 상품을 확인하는 것은 그렇게 복잡하지 않은 쿼리로 작성이 가능했다
@Query("select v.work.id " +
"from Version v " +
"where v.confirm = 'COMPLETE'" +
"group by v.work.id " +
"order by max(v.updatedDate) desc")
Page<Long> findLatestUpdateDates(Pageable pageable);
이렇게 가져오면 해당 검색 결과를 page형식의 쿼리문으로 날아가는 것을 확인할 수 가 있다.
🧨 요구사항 발생 : 롤백 데이터의 도입
사용자가 기존에 존재했던 버전을 수정하겠다고 한다면 기존의 정보를 저장해두고 수정을 할 수 있게 하고 싶다는 요구사항을 전달받았다 때문에 업데이트 관련에도 영향이 발생하게 된 것이다. 그래서 쿼리문이 변경되어야 했다
변경 사항
- 버전이 저장된 테이블에 승인이 완료된 작품을 그룹화하여 가장 나중에 업데이트 된 버전을 추출하고
- 2개의 테이블을 Union을 통해 합친다
- 위에서 한 내용을 서브쿼리로 작성하고 메인 쿼리에서 다시 똑같은 조건으로 그룹화하여 가장 나중에 업데이트 된 작품을 추출한다.
❗ 해결을 위한 방법 그리고 JPQL가 아닌 Native Query 적용
해결책 1. 각 테이블에서 전부 가져오기
- 기존의 버전테이블에서 작품을 그룹화하여 가장 최신 버전을 가져오기
- 롤백데이터 테이블에서 작품을 그룹화하여 가장 최신 버전을 가져오기
- 서비스 로직에서 직접 두개의 값을 비교하고 겹치는 값이 있다면 직접 비교해서 추리기
해결책 2. 쿼리문을 직접 작성하여 Union 사용
- 기존의 메소드에 Native Query를 통해 Union을 적용해서 두 개의 테이블을 비교해서 결과 가져오기
🎐 결정
결과를 말하자면 나는 Union을 사용하고 싶었다.
- 두개의 메소드를 작성하면 두번의 쿼리문은 필수로 적용되도록한다.
하지만 나는 결과를 받아오고 싶은거지 두개의 값을 영속성 컨텍스트에 올리고 싶은것이 아니다. - 서비스 코드의 복잡성이 증가한다.
두개의 결과를 비교하고 추리는 코드가 추가되고 이는 다른 개발자가 보면 오히려 코드가 복잡해보일수 있다.
NativeQuery를 사용한 이유
ORM은 SQL의 복잡한 쿼리를 지원하지 않기 때문에 JPQL과 QueryDSL은 Union을 지원하지 않는다. 그래서 만약 Union을 사용해서 쿼리를 작성하고 싶다면 NativeQuery를 사용해야 했다.
🔑 결과
@Query(value = "SELECT work_id, MAX(updated_date) as max_updated_date " +
"FROM (" +
" SELECT work_id, updated_date " +
" FROM work_version " +
" WHERE confirm = 'COMPLETE' " +
" UNION ALL " +
" SELECT work_id, updated_date " +
" FROM rollback_versions " +
" WHERE confirm = 'COMPLETE' " +
") combined " +
"GROUP BY work_id " +
"ORDER BY max_updated_date DESC "
, nativeQuery = true)
Slice<Object[]> findLatestUpdateDates(Pageable pageable);
쿼리를 보면 확실히 일반적인 쿼리에 비해 복잡해보이지만 사실 그렇게 막 복잡하지는 않다
FROM절에 서브 쿼리를 작성하고 Union ALL을 통해 각 테이블에 승인이 떨어진 모든 작품의 id와 업데이트 날짜를 가져온다.
그리고 메인 쿼리에서는 모든 작품을 그룹화하여 그 중 가장 빠른 업데이트를 가져오게 되는것이다
@Override
@Transactional(readOnly = true)
public Slice<WorkResponseDto> getWorkListByUpdateDate(int pageNum) {
Pageable pageable = PageRequest.of(pageNum, 10);
// 각 작품마다 가장 최신 업데이트 된 순으로 정렬
Slice<Object[]> latestUpdateDates = versionRepository.findLatestUpdateDates(pageable);
List<WorkResponseDto> workResponseDtos = latestUpdateDates.getContent().stream()
.map(result -> {
Long workId = (Long) result[0];
String name = "";
Work work = workRepository.findById(workId)
.orElseThrow(() -> new BusinessExceptionHandler(ErrorCode.WORK_NOT_FOUND));
// 제작자를 찾고 만약 없으면 미상으로 넘기기
if (work.getMember() == null) name = "미상";
else name = work.getMember().getName();
return WorkResponseDto.builder()
.workId(work.getId())
.imageUrl(work.getImageUrl())
.title(work.getTitle())
.memberName(name)
.build();
}).toList();
return new SliceImpl<>(workResponseDtos, pageable, latestUpdateDates.hasNext());
}
그러면 이렇게 서비스 로직이 굉장히 편해진다. 쿼리문의 결과는 내가 원하는 가장 최근에 업데이트된 작품을 가져오게 되는 것이다. 그리고 밑에서는 반환을 위해 Response를 구성해주는 것인데 여기서 굳이 work를 한번더 찾는데 이유는 id만 찾아서 가져오기 때문에 그 id로 work를 가져와야한다. 그래서 추가적인 쿼리가 발생하기는 하는데 사실 쿼리를 단 한개로 모든 정보를 가져오는것이 좋지만은 아니라서 이정도의 쿼리로 적용하려고 한다
'프로젝트 > 스위프 프로젝트(Lit Map)' 카테고리의 다른 글
🎋 조회수 관리(동시성 관리)를 위한 @Modifying 사용 (1) | 2024.07.23 |
---|---|
🎋 쿼리 튜닝을 통한 응답 속도 개선 ( feat. QueryDSL, JPQL ) (0) | 2024.07.21 |