Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

템플릿 단건 조회/ 템플릿 삭제/ 태그 조회 성능 개선 #704

Merged
merged 23 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
46c0e92
refactor(template): 템플릿 조회 시 성능 개선을 위해 로직 변경
kyum-q Sep 25, 2024
24af44b
refactor(Category): 카테고리에 멤버 id 인덱스 설정 추가
kyum-q Sep 25, 2024
e8810e7
refactor(template): 소스 코드 삭제 로직 @Modifying 로 하나의 sql로 전체 삭제
kyum-q Sep 26, 2024
b052311
refactor(template): 템플릿 태그 삭제 로직 @Modifying 로 하나의 sql로 전체 삭제
kyum-q Sep 26, 2024
4141d05
refactor(template): 썸네일 삭제 로직 @Modifying 로 하나의 sql로 전체 삭제
kyum-q Sep 26, 2024
c4a3f13
refactor(service): 태그 템플릿 조회 join으로 N+1쿼리 해결
kyum-q Sep 26, 2024
86d4336
refactor(tag): tag 조회 시 필요한 템플릿 id 만 받도록 수정
kyum-q Sep 26, 2024
02083ee
refactor(tag): IN 절 대신 서브 쿼리 활용
kyum-q Sep 26, 2024
760fa24
refactor(tag): Tag N+1 문제를 내부 쿼리로 해결
kyum-q Sep 26, 2024
52d5a54
refactor(tag): Tag N+1 문제를 내부 쿼리로 해결
kyum-q Sep 26, 2024
50f37cc
Merge branch 'refactor/673-performance-template-find-delete' of https…
kyum-q Sep 26, 2024
50606e4
test: 비즈니스 로직 변경에 따른 테스트 코드 수정
kyum-q Sep 26, 2024
af374ce
feat(migration): 카테고리 memberId 인덱스 추가 마이그레이션 파일 생성
kyum-q Sep 26, 2024
9c44936
refactor(domain): FetchType.LAZY 설정 추가
kyum-q Sep 26, 2024
a36aa7a
refactor(repository): 메서드명 변경
kyum-q Sep 26, 2024
7256a88
refactor(service): 사용하지 않는 필드 제거
kyum-q Sep 26, 2024
8cc15a2
refactor(repository): 사용하지 않는 메서드 제거
kyum-q Sep 26, 2024
9dd2098
test: displayName 수정
kyum-q Sep 26, 2024
1a6ffeb
refactor(service): 불필요한 스트림 생성 제거
kyum-q Sep 27, 2024
379e913
refactor(domain): @Table 수정
kyum-q Sep 27, 2024
76083a5
Merge branch 'dev/be' into refactor/673-performance-template-find-delete
kyum-q Sep 27, 2024
283bdb4
fix: Fake DB 문제 해결
kyum-q Sep 27, 2024
5517efe
fix(repository): 코드 충돌 해결
kyum-q Sep 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions backend/src/main/java/codezap/category/domain/Category.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Index;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
Expand All @@ -24,12 +26,11 @@
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Table(
uniqueConstraints = {
@UniqueConstraint(
name = "name_with_member",
columnNames = {"member_id", "name"}
)
}
uniqueConstraints = @UniqueConstraint(
name = "name_with_member",
columnNames = {"member_id", "name"}
),
indexes = @Index(name = "idx_member_id", columnList = "member_id")
)
@Getter
@EqualsAndHashCode(of = "id", callSuper = false)
Expand All @@ -41,7 +42,7 @@ public class Category extends BaseTimeEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(optional = false)
@ManyToOne(fetch = FetchType.LAZY, optional = false)
private Member member;

@Column(nullable = false)
Expand Down
3 changes: 2 additions & 1 deletion backend/src/main/java/codezap/likes/domain/Likes.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package codezap.likes.domain;

import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
Expand Down Expand Up @@ -29,6 +30,6 @@ public class Likes extends BaseTimeEntity {
@ManyToOne(optional = false)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

단순 궁금증인데 전체적으로 지연 로딩을 적용하기로 했다고 했었는데 그거는 나중에 전체적으로 한번에 반영하는건가요??
아니면 담당한 사람이 확인??

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

나도 의문... ㅎㅎㅎㅎㅎㅎㅎ

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

헉스... 다음 스프린트에 반드시 적용해야겠습니다

private Template template;

@ManyToOne(optional = false)
@ManyToOne(fetch = FetchType.LAZY, optional = false)
private Member member;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,23 @@
import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;

import codezap.tag.domain.Tag;
import codezap.template.domain.Template;
import codezap.template.domain.TemplateTag;

@SuppressWarnings("unused")
public interface TemplateTagJpaRepository extends TemplateTagRepository, JpaRepository<TemplateTag, Long> {

List<TemplateTag> findAllByTemplate(Template template);
@Query("""
SELECT t
FROM Tag t
JOIN TemplateTag tt ON t.id = tt.id.tagId
WHERE tt.template = :template
""")
List<Tag> findAllTagsByTemplate(Template template);

@Query("""
SELECT tt, t
Expand All @@ -30,11 +38,18 @@ public interface TemplateTagJpaRepository extends TemplateTagRepository, JpaRepo
List<TemplateTag> findAllByTemplateIdsIn(List<Long> templateIds);

@Query("""
SELECT DISTINCT tt.id.tagId
FROM TemplateTag tt
WHERE tt.id.templateId IN :templateIds
""")
List<Long> findDistinctByTemplateIn(List<Long> templateIds);
SELECT DISTINCT t
FROM Tag t
WHERE t.id IN (
SELECT DISTINCT tt.id.tagId
FROM TemplateTag tt
WHERE tt.id.templateId IN
(SELECT te.id FROM Template te WHERE te.member.id = :memberId)
)
""")
List<Tag> findAllTagDistinctByMemberId(Long memberId);

void deleteAllByTemplateId(Long id);
@Modifying(clearAutomatically = true)
zangsu marked this conversation as resolved.
Show resolved Hide resolved
@Query("DELETE FROM TemplateTag t WHERE t.template.id in :templateIds")
void deleteByTemplateIds(List<Long> templateIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@

import java.util.List;

import codezap.tag.domain.Tag;
import codezap.template.domain.Template;
import codezap.template.domain.TemplateTag;

public interface TemplateTagRepository {

List<TemplateTag> findAllByTemplate(Template template);

List<Tag> findAllTagsByTemplate(Template template);

List<Tag> findAllTagDistinctByMemberId(Long memberId);

List<TemplateTag> findAllByTemplateId(Long templateId);

List<Long> findDistinctByTemplateIn(List<Long> templateIds);
Expand All @@ -19,5 +24,7 @@ public interface TemplateTagRepository {

<S extends TemplateTag> List<S> saveAll(Iterable<S> entities);

void deleteAllByTemplateId(Long id);
void deleteAllByTemplateId(Long templateId);

void deleteByTemplateIds(List<Long> templateIds);
}
18 changes: 6 additions & 12 deletions backend/src/main/java/codezap/tag/service/TagService.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,13 @@
import codezap.tag.repository.TemplateTagRepository;
import codezap.template.domain.Template;
import codezap.template.domain.TemplateTag;
import codezap.template.repository.TemplateRepository;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class TagService {

private final TagRepository tagRepository;
private final TemplateRepository templateRepository;
private final TemplateTagRepository templateTagRepository;

@Transactional
Expand All @@ -47,9 +45,7 @@ public void createTags(Template template, List<String> tagNames) {
}

public List<Tag> findAllByTemplate(Template template) {
return templateTagRepository.findAllByTemplate(template).stream()
.map(TemplateTag::getTag)
.toList();
return templateTagRepository.findAllTagsByTemplate(template);
}

public List<Tag> findAllByTemplateId(Long templateId) {
Expand All @@ -64,11 +60,9 @@ public List<TemplateTag> getAllTemplateTagsByTemplates(List<Template> templates)
}

public FindAllTagsResponse findAllByMemberId(Long memberId) {
List<Template> templates = templateRepository.findByMemberId(memberId);
List<Long> templateIds = templates.stream().map(Template::getId).toList();
List<Long> templateTagIds = templateTagRepository.findDistinctByTemplateIn(templateIds);
return new FindAllTagsResponse(templateTagIds.stream()
.map(id -> FindTagResponse.from(tagRepository.fetchById(id)))
List<Tag> tags = templateTagRepository.findAllTagDistinctByMemberId(memberId);
return new FindAllTagsResponse(tags.stream()
.map(FindTagResponse::from)
.toList());
}

Expand All @@ -79,7 +73,7 @@ public void updateTags(Template template, List<String> tags) {
}

@Transactional
public void deleteByIds(List<Long> templateIds) {
templateIds.forEach(templateTagRepository::deleteAllByTemplateId);
public void deleteAllByTemplateIds(List<Long> templateIds) {
templateTagRepository.deleteByTemplateIds(templateIds);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import java.util.Optional;

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

import codezap.global.exception.CodeZapException;
Expand Down Expand Up @@ -31,5 +33,7 @@ default SourceCode fetchByTemplateAndOrdinal(Template template, int ordinal) {

int countByTemplate(Template template);

void deleteByTemplateId(Long id);
@Modifying(clearAutomatically = true)
@Query("DELETE FROM SourceCode s WHERE s.template.id in :templateIds")
void deleteByTemplateIds(List<Long> templateIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@ public interface SourceCodeRepository {

void deleteById(Long id);

void deleteByTemplateId(Long id);
void deleteByTemplateIds(List<Long> templateIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.Optional;

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

Expand All @@ -29,6 +30,11 @@ default Thumbnail fetchByTemplate(Template template) {
""")
Optional<Thumbnail> findByTemplate(Template template);


@Modifying(clearAutomatically = true)
@Query("DELETE FROM Thumbnail t WHERE t.template.id in :templateIds")
void deleteByTemplateIds(List<Long> templateIds);

@Query("""
SELECT t, sc
FROM Thumbnail t
Expand All @@ -38,4 +44,5 @@ default Thumbnail fetchByTemplate(Template template) {
List<Thumbnail> findAllByTemplateIn(List<Long> templateIds);

void deleteByTemplateId(Long id);

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ public interface ThumbnailRepository {

Thumbnail save(Thumbnail thumbnail);

void deleteByTemplateId(Long id);
void deleteByTemplateIds(List<Long> ids);
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ private void validateSourceCodesCount(Template template, UpdateTemplateRequest u
}

@Transactional
public void deleteByIds(List<Long> templateIds) {
templateIds.forEach(sourceCodeRepository::deleteByTemplateId);
public void deleteByTemplateIds(List<Long> templateIds) {
sourceCodeRepository.deleteByTemplateIds(templateIds);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,6 @@ public ExploreTemplatesResponse findAll() {

@Transactional
public void deleteByTemplateIds(List<Long> templateIds) {
templateIds.forEach(thumbnailRepository::deleteByTemplateId);
thumbnailRepository.deleteByTemplateIds(templateIds);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ public void update(Member member, Long templateId, UpdateTemplateRequest updateT
@Transactional
public void deleteByMemberAndIds(Member member, List<Long> ids) {
thumbnailService.deleteByTemplateIds(ids);
sourceCodeService.deleteByIds(ids);
tagService.deleteByIds(ids);
sourceCodeService.deleteByTemplateIds(ids);
tagService.deleteAllByTemplateIds(ids);
templateService.deleteByMemberAndIds(member, ids);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE INDEX idx_member_id ON category(member_id);
Original file line number Diff line number Diff line change
Expand Up @@ -51,24 +51,24 @@ void setUp() {
}

@Test
@DisplayName("Template 을 이용한 TemplateTag 목록 조회 성공")
void findAllByTemplateTest() {
@DisplayName("Template 을 이용한 Tag 목록 조회 성공")
void findAllTagsByTemplateTest() {
//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));
templateTagRepository.save(new TemplateTag(template, tag1));
templateTagRepository.save(new TemplateTag(template, tag2));

//when
List<TemplateTag> templateTags = templateTagRepository.findAllByTemplate(template);
List<Tag> tags = templateTagRepository.findAllTagsByTemplate(template);

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


Expand Down Expand Up @@ -126,8 +126,9 @@ class FindDistinctByTemplateIn {
@DisplayName("태그 목록 조회 성공 : 정상적으로 태그 목록 조회")
void testFindDistinctByTemplateIn() {
// given
Member otherMember = memberRepository.save(MemberFixture.getSecondMember());
Template template1 = templateRepository.save(createNthTemplate(member, category, 1));
Template template2 = templateRepository.save(createNthTemplate(member, category, 2));
Template template2 = templateRepository.save(createNthTemplate(otherMember, category, 2));
Template template3 = templateRepository.save(createNthTemplate(member, category, 3));

Tag tag1 = tagRepository.save(new Tag("tag1"));
Expand All @@ -140,18 +141,16 @@ void testFindDistinctByTemplateIn() {
templateTagRepository.save(new TemplateTag(template3, tag3));

// when
List<Long> result = templateTagRepository.findDistinctByTemplateIn(
List.of(template1.getId(), template3.getId())
);
List<Tag> result = templateTagRepository.findAllTagDistinctByMemberId(member.getId());

// then
assertThat(result).hasSize(2)
.containsExactly(tag1.getId(), tag3.getId())
.doesNotContain(tag2.getId());
.containsExactly(tag1, tag3)
.doesNotContain(tag2);
}

@Test
@DisplayName("태그 목록 조회 성공 : 존재하지 않는 템플릿 id를 사용하면 예외가 터지지 않고 해당 id 값이 무시된다.")
@DisplayName("태그 목록 조회 성공 : 존재하지 않는 멤버 id를 사용하면 예외가 터지지 않고 해당 id 값이 무시된다.")
void notExistTemplateIdTest() {
long notExistTemplateId = 100L;

Expand All @@ -160,9 +159,9 @@ void notExistTemplateIdTest() {
.isInstanceOf(CodeZapException.class)
.hasMessageContaining("템플릿이 존재하지 않습니다."),
() -> assertThatCode(() ->
templateTagRepository.findDistinctByTemplateIn(List.of(notExistTemplateId)))
templateTagRepository.findAllTagDistinctByMemberId(notExistTemplateId))
.doesNotThrowAnyException(),
() -> assertThat(templateTagRepository.findDistinctByTemplateIn(List.of(notExistTemplateId)))
() -> assertThat(templateTagRepository.findAllTagDistinctByMemberId(notExistTemplateId))
.isEmpty()
);
}
Expand Down
Loading