기존 코드 점검


@Override
    public RecruitmentDetailDto getDetailDto(Long recruitmentId) {
        RecruitmentDetailDto recruitmentDetailDto = queryFactory
                .select(new QRecruitmentDetailDto(
                        recruitment.member.id,
                        recruitment.member.nickname,
                        recruitment.member.profileUrl,
                        recruitment.member.workoutPurpose,
                        recruitment.gym,
                        recruitment.id,
                        recruitment.title,
                        recruitment.description,
                        recruitment.preferGender,
                        recruitment.createdAt,
                        recruitment.startedAt))
                .from(recruitment)
                .where(
                        recruitment.id.eq(recruitmentId),
                        recruitment.isDeleted.eq(false)
                )
                .fetchOne();

        if (ObjectUtils.isEmpty(recruitmentDetailDto)) {
            return null;
        }

        List<String> imgUrls = queryFactory.select(recruitmentImg.imgUrl)
                .from(recruitmentImg)
                .where(recruitmentImg.recruitment.id.eq(recruitmentId))
                .fetch();

        recruitmentDetailDto.setImgUrls(imgUrls);
        return recruitmentDetailDto;

모집글 상세를 불러오려면 모집글의 pk에 해당하는 id 번호를 알아야한다.

필요한 정보는 모집글 외에도 모집글을 작성한 회원의 정보와 헬스장 정보가 필요하다.

또한 id 번호가 일치하는 것 말고도 소프트 삭제가 되었는지 여부를 체크한 다음, 삭제 된 상태가 아니면 해당 게시글을 반환한다.

모집글 상세의 이미지 같은 경우 최대 3개까지 등록되는 컬렉션 단위이기 때문에 쿼리를 분리하였다. 만약 해당 모집글이 없다면 이미지 관련 쿼리는 실행되지 않는 구조이다.

위와 같이 작성할 경우 쿼리는 다음과 같이 나간다.

2023-06-04 13:46:01.181  INFO 4716 --- [           main] p6spy : [statement] | 21 ms | 
    select
        recruitmen0_.member_id as col_0_0_,
        member1_.nickname as col_1_0_,
        member1_.profile_url as col_2_0_,
        member1_.workout_purpose as col_3_0_,
        recruitmen0_.gym_id as col_4_0_,
        recruitmen0_.recruitment_id as col_5_0_,
        recruitmen0_.title as col_6_0_,
        recruitmen0_.description as col_7_0_,
        recruitmen0_.prefer_gender as col_8_0_,
        recruitmen0_.created_at as col_9_0_,
        recruitmen0_.started_at as col_10_0_,
        gym4_.gym_id as gym_id1_1_,
        gym4_.created_at as created_2_1_,
        gym4_.updated_at as updated_3_1_,
        gym4_.latitude as latitude4_1_,
        gym4_.location as location5_1_,
        gym4_.longitude as longitud6_1_,
        gym4_.name as name7_1_       
    from
        recruitment recruitmen0_ 
		cross join
        member member1_       
    inner join
        gym gym4_               
            on recruitmen0_.gym_id=gym4_.gym_id       
    where
        recruitmen0_.member_id=member1_.member_id           
        and recruitmen0_.recruitment_id=500000           
        and recruitmen0_.is_deleted='N'
2023-06-04 13:46:01.260  INFO 4716 --- [           main] p6spy  : [statement] | 14 ms | 
    select
        recruitmen0_.img_url as col_0_0_       
    from
        recruitment_img recruitmen0_       
    where
        recruitmen0_.recruitment_id=500000

그런데 위 쿼리에서 Join 부분이 모집글 검색에서의 조인 쿼리와 다른 점이 보인다. 아래는 모집글 리스트를 가져올 때 모집글 entity 자체를 가져오는 쿼리이다.

Fetch Join을 사용하였기 때문에 기존으로 left outer Join이 나간 것을 확인할 수 있다. 하지만 위와 같은 경우는 cross join과 inner join을 사용하여 데이터를 가져왔다.

모집글에 매칭되는 멤버는 단 하나만 존재한다. 따라서 cross join으로 인한 데이터 뻥튀기 현상은 없을 것으로 생각되나, 모집글 기준으로 left outer join을 하는 기존 방식이 훨씬 더 나아 보인다.

inner join도 마찬가지인데, 어플리케이션 로직 상 헬스장을 등록안하는 경우는 없겠지만, 만약 헬스장이 등록되지 않는 모집글이라면, inner join을 했을 때 아무런 데이터를 받지 못할 것이다. join 쿼리가 예상과 다르게 나가기 때문에 개선 작업이 필요해보인다.