[트러블슈팅] ILikeYou 필터링 및 정렬 쿼리 JPQL -> Querydsl 코드개선작업

2023. 8. 1. 16:54트러블슈팅

문제점 발견

기존 필터링,정렬 코드

private List<LikeablePerson> findByIdFilteredAndSortedList(Long instaMemberId, int sortCode, String gender, Integer attractiveTypeCode) {
        switch (sortCode) {
            case 1:
                return likeablePersonRepository.findByIdFilteredAndSortedOrderByCreateDateDesc(instaMemberId, gender, attractiveTypeCode);
            case 2:
                return likeablePersonRepository.findByIdFilteredAndSortedOrderByHotOfFromInstaMemberAsc(instaMemberId, gender, attractiveTypeCode);
            case 3:
                return likeablePersonRepository.findByIdFilteredAndSortedOrderByHotOfFromInstaMemberDesc(instaMemberId, gender, attractiveTypeCode);
            case 4:
                return likeablePersonRepository.findByIdFilteredAndSortedOrderByGenderOfFromInstaMemberAsc(instaMemberId, gender, attractiveTypeCode);
            case 5:
                return likeablePersonRepository.findByIdFilteredAndSortedOrderByAttractiveTypeCodeAsc(instaMemberId, gender, attractiveTypeCode);
            default:
                return likeablePersonRepository.findByIdFilteredAndSortedOrderByCreateDateAsc(instaMemberId, gender, attractiveTypeCode);
        }
    }
//최신순
    @Query("select L from LikeablePerson L inner join L.toInstaMember T inner join L.fromInstaMember F on T.id=:id where (:attractiveTypeCode is null or L.attractiveTypeCode=:attractiveTypeCode) and (:gender is null or F.gender=:gender) order by L.createDate asc")
    List<LikeablePerson> findByIdFilteredAndSortedOrderByCreateDateAsc(@Param("id") Long id , @Param("gender") String gender, @Param("attractiveTypeCode") Integer attractiveTypeCode);

    //오래된순
    @Query("select L from LikeablePerson L inner join L.toInstaMember T inner join L.fromInstaMember F on T.id=:id where (:attractiveTypeCode is null or L.attractiveTypeCode=:attractiveTypeCode) and (:gender is null or F.gender=:gender) order by L.createDate desc")
    List<LikeablePerson> findByIdFilteredAndSortedOrderByCreateDateDesc(@Param("id") Long id , @Param("gender") String gender, @Param("attractiveTypeCode") Integer attractiveTypeCode);

    //인기순
    @Query("select L from LikeablePerson L inner join L.toInstaMember T inner join L.fromInstaMember F on T.id=:id where (:attractiveTypeCode is null or L.attractiveTypeCode=:attractiveTypeCode) and (:gender is null or F.gender=:gender) ORDER BY SIZE(F.toLikeablePeople) desc")
    List<LikeablePerson> findByIdFilteredAndSortedOrderByHotOfFromInstaMemberAsc(@Param("id") Long id , @Param("gender") String gender, @Param("attractiveTypeCode") Integer attractiveTypeCode);

    //인기적은순
    @Query("select L from LikeablePerson L inner join L.toInstaMember T inner join L.fromInstaMember F on T.id=:id where (:attractiveTypeCode is null or L.attractiveTypeCode=:attractiveTypeCode) and (:gender is null or F.gender=:gender) ORDER BY SIZE(F.toLikeablePeople) asc")
    List<LikeablePerson> findByIdFilteredAndSortedOrderByHotOfFromInstaMemberDesc(@Param("id") Long id, @Param("gender") String gender, @Param("attractiveTypeCode") Integer attractiveTypeCode);

    //성별순
    @Query("select L from LikeablePerson L inner join L.toInstaMember T inner join L.fromInstaMember F on T.id=:id where (:attractiveTypeCode is null or L.attractiveTypeCode=:attractiveTypeCode) and (:gender is null or F.gender=:gender) order by F.gender asc")
    List<LikeablePerson> findByIdFilteredAndSortedOrderByGenderOfFromInstaMemberAsc(@Param("id") Long id, @Param("gender") String gender, @Param("attractiveTypeCode") Integer attractiveTypeCode);

    //호감사유순
    @Query("select L from LikeablePerson L inner join L.toInstaMember T inner join L.fromInstaMember F on T.id=:id where (:attractiveTypeCode is null or L.attractiveTypeCode=:attractiveTypeCode) and (:gender is null or F.gender=:gender) order by L.attractiveTypeCode asc")
    List<LikeablePerson> findByIdFilteredAndSortedOrderByAttractiveTypeCodeAsc(@Param("id") Long id, @Param("gender") String gender, @Param("attractiveTypeCode") Integer attractiveTypeCode);
}

 

위 코드는 instaMemberId에 따른 인스타계정을 호감표시한 목록을 나타내는 코드이다.

 

gender(성별)과 attractiveTypeCode(호감코드) 로 필터링을 거쳐 sortCode(정렬코드)에 따라 정렬되어서 리스팅 된다.

 

JPQL로 처리할 수 있는 것은 필터링이 한계이며, 그마저도 가독성이 떨어진다고 느껴졌다.

 

그래서 정렬에 따른 메소드를 여러 개를 구현했다.

 

한 눈에 봐도 가독성이 떨어지고 복잡도가 높은 것이 느껴진다.

 

만약 서비스가 확장되고 정렬 방식에 대한 요구사항이 들어온다면 계속해서 메서드를 늘려나가야 한다.

 

이러한 구조는 유지보수 및 확장성에도 적합하지 않다.

 

--> 개선의 여지가 있다.

 

그래서 Querydsl 을 도입하여 코드를 개선하여 가독성을 높이고자 한다.

 

Querydsl을 선택한 이유는 default값 설정이 용이하고, null값이 들어왔을 때에 대비하기가 쉬워 오류를 방지할 수 있다.

 

무엇보다 메소드 하나로 모든 필터링과 동적 정렬을 처리할 수 있기 때문이다.


개선 작업

private BooleanExpression eqGender(String gender) {
    if (StringUtils.isEmpty(gender)) {
        return null;
    }
    return likeablePerson.fromInstaMember.gender.eq(gender);
}

private BooleanExpression eqAttractiveTypeCode(Integer attractiveTypeCode) {
    if (ObjectUtils.isEmpty(attractiveTypeCode)) {
        return null;
    }
    return likeablePerson.attractiveTypeCode.eq(attractiveTypeCode);
}

위와 같은 방법으로 파라미터가 들어올 경우와 들어오지 않을 경우가 있을 수 있기에, 값의 유무 체크 후에 조건절을 검사하게 된다.

값이 없는 경우는 null을 반환함으로 해당 eq조건절을 생략한다.

private static OrderSpecifier<?>
orderSelector(Integer sortCode) {
    if (sortCode == null) {
        return likeablePerson.createDate.desc();
    }

    return switch (sortCode) {
        case 2 -> likeablePerson.fromInstaMember.toLikeablePeopleCount.asc();
        case 3 -> likeablePerson.fromInstaMember.toLikeablePeopleCount.desc();
        case 4 -> likeablePerson.fromInstaMember.gender.asc();
        case 5 -> likeablePerson.attractiveTypeCode.desc();
        default -> likeablePerson.createDate.asc();
    };
}

정렬은 OrderSpecifier 객체를 사용해서 구현했다.

 

sortCode가 따로 들어오지 않을 경우에는 null 체크를 함으로써 default인 최신순으로 정렬되도록 했다.

 

@Override
public List<LikeablePersonResponse> findByIdFilteredAndSorted(Long instaMemberId, Integer sortCode, String gender, Integer attractiveTypeCode) {

    List<LikeablePersonResponse> result = queryFactory
            .select(new QLikeablePersonResponse(
                    likeablePerson.createDate,
                    likeablePerson.id,
                    likeablePerson.fromInstaMember.id,
                    likeablePerson.toInstaMember.id,
                    likeablePerson.fromInstaMember.gender,
                    likeablePerson.attractiveTypeCode
                    )
            )
            .from(likeablePerson)
            .where(
                    likeablePerson.toInstaMember.id.eq(instaMemberId),
                    eqGender(gender),
                    eqAttractiveTypeCode(attractiveTypeCode)
            )
            .orderBy(orderSelector(sortCode))
            .fetch();

    return result;
}

전체 코드이다. Dto에 필요한 값을 바로 찾아서 매칭시켜줌으로 Entity의 캡슐화를 더 보장할 수 있었다.


다음은 리팩토링 후에 파라미터에 따라서 생성되는 쿼리를 살펴봤다.

 

아무런 필터링을 하지 않고 목록을 조회하는 경우는 아래와 같다.

where 절을 보면 반드시 필요한 id값 비교밖에 없는 것을 확인할 수 있다.

*/ select
            l1_0.create_date,
            l1_0.id,
            l1_0.from_insta_member_id,
            l1_0.to_insta_member_id,
            f1_0.gender,
            l1_0.attractive_type_code 
        from
            likeable_person l1_0 
        join
            insta_member f1_0 
                on f1_0.id=l1_0.from_insta_member_id 
        where
            l1_0.to_insta_member_id=? 
        order by
            l1_0.create_date desc

 

필터링과 정렬을 선택해보았다.

// where절을 보면 id뿐만 아니라 호감표시를 한 대상의 gender, attractiveTypeCode오 조건절에 사용되었고,
// order by에서도 sortCode에 따른 정렬 쿼리가 날라가는 것을 확인할 수 있다.

*/ select
            l1_0.create_date,
            l1_0.id,
            l1_0.from_insta_member_id,
            l1_0.to_insta_member_id,
            f1_0.gender,
            l1_0.attractive_type_code 
        from
            likeable_person l1_0 
        left join
            insta_member f1_0 
                on f1_0.id=l1_0.from_insta_member_id 
        where
            l1_0.to_insta_member_id=? 
            and f1_0.gender=? 
            and l1_0.attractive_type_code=? 
        order by
            f1_0.to_likeable_people_count desc