본문 바로가기
TIL : Today I learned (or Week)

TIL 230706 : 해시태그 조회 구현하기 (map활용 카운트하는 방식)

by 우인입니다 2023. 7. 7.
여러 태그를 모두 가지고 있는 게시물들을 반환하려고 한다.

 

문제

내가 찾고자 하는 태그를 모두 갖고 있는 Post들을 전부 가져오고 싶었다.

List를 넣어줘서 쿼리메소드에 In을 붙이면 tag들이 있는 Post를 가져올거라 생각했다.

 

하지만, 원치않은 결과들도 호출됏다.

예를들어, tagA, tagB 둘 다 가지고 있는 Post를 가져오고 싶어서

List<Tag> tagList = {tagA, tagB}; 를 해서

위 메소드에 입력하게 되면 tagA만 가지고 있는 Post도 가져오게 된다.

 

내가 원하는 건 모든 tag를 다 갖고 있는 Post들이다.

 

 


시도

1. Specification

tag를 모두 갖고 있는 쿼리를 동적으로 만들어주는 specification을 활용해보고자 했다.

사실 내가 쓰면서도 잘 모르겠다.

기본을 모른 채 사용해보려 했고, 동작하지 않았고 나는 대응하지 못 했다.

 

 

2. 그냥 다 가져온 다음에 태그 전부 갖고 있는지 검사

이거는 시도라기보단 뇌를 스쳐만 갔다.

왜냐하면 뇌속에서만 코드를 짜봐도 for문이 너무 다중으로 있었다.

만약, for문을 활용했다면 아래와 같았을 거다.

이건 O(N^3)쯤 되려나

 


해결방법

전체 프로세스

1. TagPostTable에는 post_id와 tag_id가 하나씩 들어있는 행들이 무수히 있다.

2. 여기서 내가 원하는 tag_id가 포함된 모든 tagPostTable을 다 가져온다.

3. 여기에는 post_id가 한번 나오기도 하고 중복도 될 텐데, 만약 태그 3개를 다 포함하는 post라면? 여기서 post_id가 3번 중복될 것이다.

4. post가 몇 번 나오는지 카운트해주는 map을 만든다.

5. post의 카운트 숫자와 원하는 tagList의 size()와 같은 post만을 다시 가져온다.

 

위와 같은 방식으로 O(N)을 유지하려 애썼다.

 

아래는 시도한 코드

 


1) PostRequestDto에서 가져온 List<String> tagListList<Tag> 생성

List<Tag> tagList = tagRepository.findAllByNameIn(strings);

2) List<Tag>로 해당 태그가 있는 모든 TagPostTable을 가져온다. List<TagPostTable> 생성

List<TagPostTable> tagPostTableList = tagPostTableRepository.findAllByTagInOrderByPost_CreatedAtDesc(tagList);
  • tag_id가 1,2인 경우 List<TagPostTable> 예시
    • post_id 1,2,3은 tag_id를 둘다 갖고 있지만, post_id 4,5는 tag_id가 하나이다.→ TagList의 배열의 숫자만큼 post_id가 등장하면 그 post_id는 가져와야할 Post의 PK이다.
    • → 둘다 갖고 있는 Post객체가 모든 태그를 가지고 있는 Post이다.

3) Post를 카운트해 줄 Map<Post, Integer> countMap 생성

  • 처음 createdAt 내림차순으로 가져온 순서를 지키기 위해 LinkedHashMap으로 생성
Map<Post, Integer> countMap = new LinkedHashMap<>();
for (TagPostTable tagPostTable : tagPostTableList) {
    countMap.put(tagPostTable.getPost(), countMap.getOrDefault(tagPostTable.getPost(), 0) + 1);
}

4) postList에 countMap의 값이 총 태그의 숫자와 같은 post만 가져오기

  • 최종 List<Post> postList 는 요청받은 모든 태그를 포함하는 Post들을 가지고 있다.
List<Post> postList = new ArrayList<>();
	  for (Map.Entry<Post, Integer> entry : countMap.entrySet()) {
		    if (entry.getValue() == tagList.size()) {
	      postList.add(entry.getKey());
		    }
}

 


 

솔직히 이게 되긴 했는데, 코딩 선배님이 보시면 숨겨진 기능을 통해 간단히 구현해내실 것 같다.
책을 하나 사야겠다.
그게 맞는 것 같다.
일단 창고에 막 널부러진 개념들을 정리할 시간이 필요하다.