querydsl transform 사용

 public List<MemberInfo> getMembersInfo() {
        Map<Long, MemberInfo> resultMap = query
                .from(member)
                .join(address).on(address.member.id.eq(member.id))
                .join(article).on(article.member.id.eq(member.id))
                .transform(groupBy(member.id).as(new QMemberInfo(
                        member.username,
                        list(new QMemberInfo_AddressInfo(address.fullAddress)),
                        list(new QMemberInfo_ArticleInfo(article.title))
                )));

        return resultMap.keySet().stream()
                .map(resultMap::get)
                .collect(toList());
    }

member ID를 key로 삼고 조회할 value들을→as(..) 담아 Map에 넣다

→ 이렇게하면 카다시안 곱이 발생, 중복 데이터제거하기위 해 set 사용. AddressInfo와 ArticleInfo가 중복되었음을 판별하기 위해 @EqualAndHashCode를 사용해야한다.

 public List<MemberInfo> getMembersInfo() {
        Map<Long, MemberInfo> resultMap = query
                .from(member)
                .join(address).on(address.member.id.eq(member.id))
                .join(article).on(article.member.id.eq(member.id))
                .transform(groupBy(member.id).as(new QMemberInfo(
                        member.username,
                        set(new QMemberInfo_AddressInfo(address.fullAddress)),
                        set(new QMemberInfo_ArticleInfo(article.title))
                )));

        return resultMap.keySet().stream()
                .map(resultMap::get)
                .collect(toList());
    }

(@EqualAndHashCode? → equal 메서드: 같은내용인지, HashCode 메서드 : 같은 객체인지 판별할 수 있는 메서드를 자동으로 생성하는 어노테이션)


개선 전 상태

Service (jpaMethod만 사용)

public NanaResponse.NanaDetailDto getNanaDetail(MemberInfoDto memberInfoDto, Long nanaId,
      boolean isSearch) {

    Language language = memberInfoDto.getLanguage();

    // nana 찾아서
    Nana nana = nanaRepository.findNanaById(nanaId)
        .orElseThrow(() -> new NotFoundException(ErrorCode.NANA_NOT_FOUND.getMessage()));

    // nanaTitle 찾아서
    NanaTitle nanaTitle = nanaTitleRepository.findNanaTitleByNanaAndLanguage(nana,
            language)
        .orElseThrow(() -> new NotFoundException(ErrorCode.NANA_TITLE_NOT_FOUND.getMessage()));

    if (isSearch) {
      searchService.updateSearchVolumeV1(NANA, nana.getId());
    }

    // nanaTitle에 맞는 게시물 조회
    List<NanaContent> nanaContentList = nanaContentRepository.findAllByNanaTitleOrderByNumber(
        nanaTitle);

    List<NanaResponse.NanaDetail> nanaDetails = new ArrayList<>();

    boolean isPostInFavorite = favoriteService.isPostInFavorite(memberInfoDto.getMember(), NANA,
        nanaTitle.getNana().getId());

    Category category = categoryRepository.findByContent(NANA_CONTENT)
        .orElseThrow(() -> new ServerErrorException("NANA_CONTENT에 해당하는 카테고리가 없습니다."));

    for (NanaContent nanaContent : nanaContentList) {

      List<Hashtag> hashtagList = hashtagRepository.findAllByLanguageAndCategoryAndPostId(
          language, category, nanaContent.getId());

      // 해시태그 정보 keyword 가져와서 list 형태로 바꾸기
      List<String> stringKeywordList = getStringKeywordListFromHashtagList(hashtagList);

      nanaDetails.add(
          NanaResponse.NanaDetail.builder()
              .number(nanaContent.getNumber())
              .subTitle(nanaContent.getSubTitle())
              .title(nanaContent.getTitle())
              .imageUrl(nanaContent.getImageFile().getOriginUrl())
              .content(nanaContent.getContent())
              .additionalInfoList(getAdditionalInfoFromNanaContentEntity(nanaContent))
              .hashtags(stringKeywordList)
              .build());

    }

    return NanaResponse.NanaDetailDto.builder()
        .originUrl(nanaTitle.getImageFile().getOriginUrl())
        .subHeading(nanaTitle.getSubHeading())
        .heading(nanaTitle.getHeading())
        .version(nana.getVersion())
        .notice(nanaTitle.getNotice())
        .nanaDetails(nanaDetails)
        .isFavorite(isPostInFavorite)
        .build();

  }
  
  
  // nanaContent의 AdditionalInfo dto로 바꾸기
   public List<NanaResponse.NanaAdditionalInfoDto> getAdditionalInfoFromNanaContentEntity(
      NanaContent nanaContent) {
    Set<NanaAdditionalInfo> eachInfoList = nanaContent.getInfoList();

    // 순서 보장 위해 List 형으로 바꾸고
    List<NanaAdditionalInfo> nanaAdditionalInfos = new ArrayList<>(eachInfoList);

    //DTO 형태로 변환
    List<NanaResponse.NanaAdditionalInfoDto> result = new ArrayList<>();
    for (NanaAdditionalInfo info : nanaAdditionalInfos) {
      result.add(NanaResponse.NanaAdditionalInfoDto.builder()
          .infoEmoji(info.getInfoType().toString())
          .infoKey(info.getInfoType().getDescription())
          .infoValue(info.getDescription())
          .build());
    }
    return result;
  }
  

쿼리

테스트 시간 → 859ms

Untitled


개선 1