Skip to content

Commit

Permalink
Merge pull request #703 from woowacourse-teams/refactor/673-performan…
Browse files Browse the repository at this point in the history
…ce-template-search

검색 성능 개선
  • Loading branch information
kyum-q authored Sep 27, 2024
2 parents 3c2ed35 + 5918abb commit 5d40e4c
Show file tree
Hide file tree
Showing 18 changed files with 454 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,22 @@ public interface TemplateTagJpaRepository extends TemplateTagRepository, JpaRepo

List<TemplateTag> findAllByTemplate(Template template);

@Query("""
SELECT tt, t
FROM TemplateTag tt
JOIN FETCH tt.tag t
WHERE tt.id.templateId = :templateId
""")
List<TemplateTag> findAllByTemplateId(Long templateId);

@Query("""
SELECT tt, t
FROM TemplateTag tt
JOIN FETCH tt.tag t
WHERE tt.id.templateId in :templateIds
""")
List<TemplateTag> findAllByTemplateIdsIn(List<Long> templateIds);

@Query("""
SELECT DISTINCT tt.id.tagId
FROM TemplateTag tt
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ public interface TemplateTagRepository {

List<TemplateTag> findAllByTemplate(Template template);

List<TemplateTag> findAllByTemplateId(Long templateId);

List<Long> findDistinctByTemplateIn(List<Long> templateIds);

List<TemplateTag> findAllByTemplateIdsIn(List<Long> templateIds);

TemplateTag save(TemplateTag templateTag);

<S extends TemplateTag> List<S> saveAll(Iterable<S> entities);
Expand Down
11 changes: 11 additions & 0 deletions backend/src/main/java/codezap/tag/service/TagService.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ public List<Tag> findAllByTemplate(Template template) {
.toList();
}

public List<Tag> findAllByTemplateId(Long templateId) {
return templateTagRepository.findAllByTemplateId(templateId).stream()
.map(TemplateTag::getTag)
.toList();
}

public List<TemplateTag> getAllTemplateTagsByTemplates(List<Template> templates) {
List<Long> templateIds = templates.stream().map(Template::getId).toList();
return templateTagRepository.findAllByTemplateIdsIn(templateIds);
}

public FindAllTagsResponse findAllByMemberId(Long memberId) {
List<Template> templates = templateRepository.findByMemberId(memberId);
List<Long> templateIds = templates.stream().map(Template::getId).toList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,8 @@ public TemplateTag(Template template, Tag tag) {
this.template = template;
this.tag = tag;
}

public boolean hasTemplate(Template template) {
return this.getTemplate().equals(template);
}
}
4 changes: 4 additions & 0 deletions backend/src/main/java/codezap/template/domain/Thumbnail.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,8 @@ public Thumbnail(Template template, SourceCode sourceCode) {
public void updateThumbnail(SourceCode sourceCode) {
this.sourceCode = sourceCode;
}

public boolean hasTemplate(Template template) {
return this.template.equals(template);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.JoinType;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.criteria.Subquery;
Expand Down Expand Up @@ -37,6 +38,11 @@ public Predicate toPredicate(Root<Template> root, CriteriaQuery<?> query, Criter
addTagPredicate(predicates, criteriaBuilder, root, query);
addKeywordPredicate(predicates, criteriaBuilder, root, query);

if (query.getResultType().equals(Template.class)) {
root.fetch("category", JoinType.LEFT);
root.fetch("member", JoinType.LEFT);
}

return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package codezap.template.repository;

import java.util.List;
import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.http.HttpStatus;

import codezap.global.exception.CodeZapException;
Expand All @@ -19,7 +21,21 @@ default Thumbnail fetchByTemplate(Template template) {
"식별자가 " + template.getId() + "인 템플릿에 해당하는 썸네일이 없습니다."));
}

@Query("""
SELECT t, sc
FROM Thumbnail t
join fetch t.sourceCode sc
WHERE t.template = :template
""")
Optional<Thumbnail> findByTemplate(Template template);

@Query("""
SELECT t, sc
FROM Thumbnail t
join fetch t.sourceCode sc
WHERE t.template.id IN :templateIds
""")
List<Thumbnail> findAllByTemplateIn(List<Long> templateIds);

void deleteByTemplateId(Long id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public interface ThumbnailRepository {

List<Thumbnail> findAll();

List<Thumbnail> findAllByTemplateIn(List<Long> templateIds);

Thumbnail save(Thumbnail thumbnail);

void deleteByTemplateId(Long id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ public Thumbnail getByTemplate(Template template) {
return thumbnailRepository.fetchByTemplate(template);
}

public List<Thumbnail> getAllByTemplates(List<Template> templates) {
List<Long> templateIds = templates.stream()
.map(Template::getId)
.toList();

return thumbnailRepository.findAllByTemplateIn(templateIds);
}

public ExploreTemplatesResponse findAll() {
return ExploreTemplatesResponse.from(thumbnailRepository.findAll());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import codezap.tag.service.TagService;
import codezap.template.domain.SourceCode;
import codezap.template.domain.Template;
import codezap.template.domain.TemplateTag;
import codezap.template.domain.Thumbnail;
import codezap.template.dto.request.CreateTemplateRequest;
import codezap.template.dto.request.UpdateTemplateRequest;
Expand Down Expand Up @@ -91,19 +92,43 @@ public FindAllTemplatesResponse findAllByWithMember(
}

private FindAllTemplatesResponse makeResponse(Page<Template> page, LikedChecker likedChecker) {
List<FindAllTemplateItemResponse> findAllTemplateByResponse = page.stream()
.map(template -> FindAllTemplateItemResponse.of(
template,
tagService.findAllByTemplate(template),
thumbnailService.getByTemplate(template).getSourceCode(),
likedChecker.isLiked(template)))
.toList();
List<Template> templates = page.getContent();
List<FindAllTemplateItemResponse> findAllTemplateByResponse = getFindAllTemplateItemResponses(templates, likedChecker);

return new FindAllTemplatesResponse(
page.getTotalPages(),
page.getTotalElements(),
findAllTemplateByResponse);
}

private List<FindAllTemplateItemResponse> getFindAllTemplateItemResponses(List<Template> templates, LikedChecker likedChecker) {
List<TemplateTag> allTemplateTagsByTemplates = tagService.getAllTemplateTagsByTemplates(templates);
List<Thumbnail> allThumbnailsByTemplates = thumbnailService.getAllByTemplates(templates);

return templates.stream()
.map(template -> FindAllTemplateItemResponse.of(
template,
getTagByTemplate(allTemplateTagsByTemplates, template),
getThumbnailSourceCodeByTemplate(allThumbnailsByTemplates, template),
likedChecker.isLiked(template)))
.toList();
}

private List<Tag> getTagByTemplate(List<TemplateTag> templateTags, Template template) {
return templateTags.stream()
.filter(templateTag -> templateTag.hasTemplate(template))
.map(TemplateTag::getTag)
.toList();
}

private SourceCode getThumbnailSourceCodeByTemplate(List<Thumbnail> thumbnails, Template template) {
return thumbnails.stream()
.filter(thumbnail -> thumbnail.hasTemplate(template))
.findFirst()
.map(Thumbnail::getSourceCode)
.orElseGet(() -> thumbnailService.getByTemplate(template).getSourceCode());
}

@Transactional
public void update(Member member, Long templateId, UpdateTemplateRequest updateTemplateRequest) {
Category category = categoryService.fetchById(updateTemplateRequest.categoryId());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,53 @@ void findAllByTemplateTest() {
.doesNotContain(new TemplateTag(template, tag3));
}


@Test
@DisplayName("Template Id 을 이용한 TemplateTag 목록 조회 성공")
void findAllByTemplateIdTest() {
//given
Template template = templateRepository.save(createNthTemplate(member, category, 1));

Tag tag1 = tagRepository.save(new Tag("tag1"));
Tag tag2 = tagRepository.save(new Tag("tag2"));
Tag tag3 = tagRepository.save(new Tag("tag3"));

TemplateTag templateTag1 = templateTagRepository.save(new TemplateTag(template, tag1));
TemplateTag templateTag2 = templateTagRepository.save(new TemplateTag(template, tag2));

//when
List<TemplateTag> templateTags = templateTagRepository.findAllByTemplateId(template.getId());

//then
assertThat(templateTags).containsExactly(templateTag1, templateTag2)
.doesNotContain(new TemplateTag(template, tag3));
}

@Test
@DisplayName("Template Id 목록 중 하나라도 일치하는 TemplateTag 목록 조회 성공")
void findAllByTemplateIdsInTest() {
//given
Template template1 = templateRepository.save(createNthTemplate(member, category, 1));
Template template2 = templateRepository.save(createNthTemplate(member, category, 1));

Tag tag1 = tagRepository.save(new Tag("tag1"));
Tag tag2 = tagRepository.save(new Tag("tag2"));
Tag tag3 = tagRepository.save(new Tag("tag3"));

TemplateTag templateTag1 = templateTagRepository.save(new TemplateTag(template1, tag1));

TemplateTag templateTag2 = templateTagRepository.save(new TemplateTag(template2, tag2));

//when
List<TemplateTag> templateTags = templateTagRepository.findAllByTemplateIdsIn(
List.of(template1.getId(), template2.getId())
);

//then
assertThat(templateTags).containsExactly(templateTag1, templateTag2)
.doesNotContain(new TemplateTag(template1, tag3));
}

@Nested
@DisplayName("템플릿 id 목록이 사용하는 모든 태그 목록을 조회")
class FindDistinctByTemplateIn {
Expand Down
86 changes: 84 additions & 2 deletions backend/src/test/java/codezap/tag/service/TagServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,88 @@ void findAllByTemplate_WhenNotExistTemplate() {
}
}

@Nested
@DisplayName("템플릿 Id로 태그 조회")
class FindAllByTemplateId {

@Test
@DisplayName("성공: 템플릿 Id에 해당하는 태그 목록 반환")
void findAllByTemplate() {
// given
Template template = createSavedTemplate();
Tag tag1 = tagRepository.save(new Tag("tag1"));
Tag tag2 = tagRepository.save(new Tag("tag2"));
TemplateTag templateTag1 = templateTagRepository.save(new TemplateTag(template, tag1));
TemplateTag templateTag2 = templateTagRepository.save(new TemplateTag(template, tag2));

// when & then
assertThat(sut.findAllByTemplateId(template.getId()))
.containsExactly(templateTag1.getTag(), templateTag2.getTag());
}

@Test
@DisplayName("성공: 템플릿에 해당하는 태그가 없는 경우 빈 목록 반환")
void findAllByTemplate_WhenNotExistTemplateTag() {
// given
Template template = createSavedTemplate();
tagRepository.save(new Tag("tag1"));
tagRepository.save(new Tag("tag2"));

// when & then
assertThat(sut.findAllByTemplateId(template.getId())).isEmpty();
}

@Test
@Disabled("현재 InvalidDataAccessApiUsageException 발생하므로 조회 직전에 검증 처리가 필요")
@DisplayName("실패: 존재하지 않는 템플릿으로 태그 조회")
void findAllByTemplate_WhenNotExistTemplate() {
// given
Member member = memberRepository.save(MemberFixture.getFirstMember());
Category category = categoryRepository.save(CategoryFixture.getFirstCategory());
Template unSavedTemplate = TemplateFixture.get(member, category);
tagRepository.save(new Tag("tag1"));
tagRepository.save(new Tag("tag2"));

// when & then
assertThatThrownBy(() -> sut.findAllByTemplateId(unSavedTemplate.getId()))
.isInstanceOf(CodeZapException.class)
.hasMessage("템플릿이 존재하지 않아 태그를 조회할 수 없습니다.");
}
}

@Nested
@DisplayName("템플릿 목록으로 템플릿 태그 조회")
class GetAllTemplateTagsByTemplates {

@Test
@DisplayName("성공: 템플릿 목록에 하나라도 해당하는 템플릿 태그 목록 반환")
void getAllTemplateTagsByTemplates() {
// given
Template template = createSavedTemplate();
Template secondTemplate = createSecondTemplate();
Tag tag1 = tagRepository.save(new Tag("tag1"));
Tag tag2 = tagRepository.save(new Tag("tag2"));
TemplateTag templateTag1 = templateTagRepository.save(new TemplateTag(template, tag1));
TemplateTag templateTag2 = templateTagRepository.save(new TemplateTag(secondTemplate, tag2));

// when & then
assertThat(sut.getAllTemplateTagsByTemplates(List.of(template, secondTemplate)))
.containsExactly(templateTag1, templateTag2);
}

@Test
@DisplayName("성공: 템플릿 목록에 전혀 해당하는 템플릿 태그 빈 목록 반환")
void getAllTemplateTagsByTemplates_WhenNotExistTemplateTag() {
// given
Template template = createSavedTemplate();
tagRepository.save(new Tag("tag1"));
tagRepository.save(new Tag("tag2"));

// when & then
assertThat(sut.getAllTemplateTagsByTemplates(List.of(template))).isEmpty();
}
}

@Nested
@DisplayName("사용자 ID로 모든 태그 조회")
class FindAllByMemberId {
Expand Down Expand Up @@ -200,8 +282,8 @@ void findAllByTemplates() {

// when & then
List<Tag> actual = new ArrayList<>();
actual.addAll(sut.findAllByTemplate(template1));
actual.addAll(sut.findAllByTemplate(template2));
actual.addAll(sut.findAllByTemplateId(template1.getId()));
actual.addAll(sut.findAllByTemplateId(template2.getId()));
assertThat(actual).isEqualTo(List.of(tag1, tag2));
}

Expand Down
Loading

0 comments on commit 5d40e4c

Please sign in to comment.