Skip to content

Commit

Permalink
#193 Refactor: TastingNote 조회 성능 최적화 (#194)
Browse files Browse the repository at this point in the history
  • Loading branch information
jeongyun1206 authored Nov 18, 2024
2 parents fe45eb0 + 508d82e commit b9c6ef0
Show file tree
Hide file tree
Showing 12 changed files with 98 additions and 41 deletions.
3 changes: 2 additions & 1 deletion src/main/java/com/drinkeg/drinkeg/domain/Wine.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ public class Wine {
private int price;

// cascade = CascadeType.ALL : 와인이 저장될 때 같이 저장됨
@OneToOne(mappedBy = "wine", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "wine_id")
private WineNote wineNote;

@Builder.Default
Expand Down
6 changes: 1 addition & 5 deletions src/main/java/com/drinkeg/drinkeg/domain/WineNote.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
package com.drinkeg.drinkeg.domain;

import com.drinkeg.drinkeg.converter.StringIntegerMapConverter;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import static jakarta.persistence.FetchType.LAZY;

@Entity
@Getter
@Builder
Expand All @@ -19,8 +16,7 @@ public class WineNote extends BaseEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@OneToOne(fetch = LAZY)
@JoinColumn(name = "wine_id")
@OneToOne(mappedBy = "wineNote", fetch = FetchType.LAZY) // 연관 관계의 비주인
private Wine wine;

// 점수 평균
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import java.util.Optional;

public interface MemberRepository extends JpaRepository<Member, Long> {
public interface MemberRepository extends JpaRepository<Member, Long>{

Optional<Member> findByUsername(String username);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

import java.util.List;

public interface TastingNoteRepository extends JpaRepository<TastingNote, Long> {
public interface TastingNoteRepository extends JpaRepository<TastingNote, Long>, TastingNoteRepositoryCustom {

List<TastingNote> findAllByWine(Wine wine);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.drinkeg.drinkeg.repository;

import com.drinkeg.drinkeg.domain.TastingNote;

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

public interface TastingNoteRepositoryCustom {

Optional<TastingNote> findTastingNoteWithWineAndNoseById(Long tastingNoteId);

Optional<TastingNote> findTastingNoteWithNoseById(Long tastingNoteId);

List<TastingNote> findTastingNotesWithWineAndNoseByUsername(String username);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.drinkeg.drinkeg.repository;

import com.drinkeg.drinkeg.domain.QMember;
import com.drinkeg.drinkeg.domain.TastingNote;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

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

import static com.drinkeg.drinkeg.domain.QMember.member;
import static com.drinkeg.drinkeg.domain.QTastingNote.tastingNote;
import static com.drinkeg.drinkeg.domain.QTastingNoteNose.tastingNoteNose;
import static com.drinkeg.drinkeg.domain.QWine.wine;

@Repository
@RequiredArgsConstructor
@Transactional
public class TastingNoteRepositoryImpl implements TastingNoteRepositoryCustom{

private final JPAQueryFactory queryFactory;

@Override
public Optional<TastingNote> findTastingNoteWithWineAndNoseById(Long tastingNoteId) {
return Optional.ofNullable(
queryFactory.selectFrom(tastingNote)
.leftJoin(tastingNote.wine, wine).fetchJoin()
.leftJoin(tastingNote.noseList, tastingNoteNose).fetchJoin()
.where(tastingNote.id.eq(tastingNoteId))
.fetchOne()
);
}
@Override
public Optional<TastingNote> findTastingNoteWithNoseById(Long tastingNoteId) {
return Optional.ofNullable(
queryFactory.selectFrom(tastingNote)
.leftJoin(tastingNote.noseList, tastingNoteNose).fetchJoin()
.where(tastingNote.id.eq(tastingNoteId))
.fetchOne()
);
}

@Override
public List<TastingNote> findTastingNotesWithWineAndNoseByUsername(String username) {
return queryFactory.selectFrom(tastingNote)
.leftJoin(tastingNote.member, member).fetchJoin()
.leftJoin(tastingNote.wine, wine).fetchJoin()
.leftJoin(tastingNote.noseList, tastingNoteNose).fetchJoin()
.where(member.username.eq(username))
.fetch();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ public class WineNoteRepositoryImpl implements WineNoteRepositoryCustom {

// WineNote 업데이트 메서드
public void updateWineNoteStatistics(Long wineId) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();

// 평균 점수 한 번에 계산 및 null 안전 처리
Tuple result = queryFactory
Expand Down Expand Up @@ -83,9 +81,6 @@ public void updateWineNoteStatistics(Long wineId) {
.set(wineNote.avgSatisfaction, avgSatisfaction)
.set(wineNote.nose, nose)
.execute();

stopWatch.stop();
System.out.println(stopWatch.getTime());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
import com.drinkeg.drinkeg.repository.WineLectureCompleteRepository;
import com.drinkeg.drinkeg.repository.WineLectureRepository;
import com.drinkeg.drinkeg.service.memberService.MemberService;
import com.drinkeg.drinkeg.service.wineClassProgressService.WineClassProgressService;
import com.drinkeg.drinkeg.service.wineLectureService.WineLectureService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ public interface MemberService {
public Member getMemberByUsername(String username);

public Member loadMemberByPrincipalDetail(PrincipalDetail principalDetail);

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,4 @@ public Member loadMemberByPrincipalDetail(PrincipalDetail principalDetail) {
return memberRepository.findByUsername(username).orElseThrow(()
-> new GeneralException(ErrorStatus.MEMBER_NOT_FOUND));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
import com.drinkeg.drinkeg.event.wineNoteEvent.WineNoteUpdateEvent;
import com.drinkeg.drinkeg.dto.loginDTO.commonDTO.PrincipalDetail;
import com.drinkeg.drinkeg.exception.GeneralException;
import com.drinkeg.drinkeg.repository.MemberRepository;
import com.drinkeg.drinkeg.repository.TastingNoteNoseRepository;
import com.drinkeg.drinkeg.repository.TastingNoteRepository;
import com.drinkeg.drinkeg.service.memberService.MemberService;
import com.drinkeg.drinkeg.service.wineService.WineService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -37,39 +37,36 @@ public class TastingNoteServiceImpl implements TastingNoteService {

private final ApplicationEventPublisher eventPublisher;

private final MemberService memberService;
private final WineService wineService;
private final MemberRepository memberRepository;

@Override
public void saveTastingNote(TastingNoteRequestDTO tastingNoteRequestDTO, PrincipalDetail principalDetail) {

// 회원을 조회한다.
Member member = memberService.loadMemberByPrincipalDetail(principalDetail);
Member member = memberRepository.findByUsername(principalDetail.getUsername()).orElseThrow(
() -> new GeneralException(ErrorStatus.MEMBER_NOT_FOUND)
);

// 와인을 찾는다.
Long wineId = tastingNoteRequestDTO.getWineId();
Wine wine = wineService.findWineById(wineId);

// TastingNote를 저장한다.
com.drinkeg.drinkeg.domain.TastingNote tastingNote = tastingNoteRepository
.save(TastingNoteConverter.toTastingNoteEntity(tastingNoteRequestDTO, member, wine));
tastingNoteRepository.save(TastingNoteConverter.toTastingNoteEntity(tastingNoteRequestDTO, member, wine));

eventPublisher.publishEvent(new WineNoteUpdateEvent(wineId));
}

@Override
public TastingNoteResponseDTO showTastingNoteById(Long noteId, PrincipalDetail principalDetail) {

// 회원을 조회한다.
Member member = memberService.loadMemberByPrincipalDetail(principalDetail);

// noteId로 TastingNote를 찾는다.
TastingNote foundNote = tastingNoteRepository.findById(noteId).orElseThrow(()
TastingNote foundNote = tastingNoteRepository.findTastingNoteWithWineAndNoseById(noteId).orElseThrow(()
-> new GeneralException(ErrorStatus.TASTING_NOTE_NOT_FOUND)
);

// TastingNote의 Member가 요청한 Member와 같은지 확인한다.
if(!foundNote.getMember().equals(member)) {
if(!foundNote.getMember().getUsername().equals(principalDetail.getUsername())) {
throw new GeneralException(ErrorStatus.TASTING_NOTE_FORBIDDEN);
}

Expand All @@ -80,11 +77,9 @@ public TastingNoteResponseDTO showTastingNoteById(Long noteId, PrincipalDetail
@Override
public AllTastingNoteResponseDTO findAllTastingNote(String sort, PrincipalDetail principalDetail) {

// 회원을 조회한다.
Member member = memberService.loadMemberByPrincipalDetail(principalDetail);

// Member의 TastingNote를 찾는다.
List<com.drinkeg.drinkeg.domain.TastingNote> foundNotes = member.getTastingNotes();
// username 을 이용해서 TastingNotes 조회
// TastingNotes 조회 시 Wine, WineNose fetch join 하여 최적화
List<TastingNote> foundNotes= tastingNoteRepository.findTastingNotesWithWineAndNoseByUsername(principalDetail.getUsername());

int total = foundNotes.size();
int red = (int) foundNotes.stream().filter((note) -> note.getWine().getSort().contains("레드")).count();
Expand Down Expand Up @@ -129,10 +124,12 @@ private boolean filterBySort(com.drinkeg.drinkeg.domain.TastingNote note, String
public void updateTastingNote(Long noteId, TastingNoteUpdateRequestDTO tastingNoteUpdateRequestDTO, PrincipalDetail principalDetail) {

// 회원을 조회한다.
Member member = memberService.loadMemberByPrincipalDetail(principalDetail);
Member member = memberRepository.findByUsername(principalDetail.getUsername()).orElseThrow(
() -> new GeneralException(ErrorStatus.MEMBER_NOT_FOUND)
);

// noteId로 TastingNote를 찾는다.
com.drinkeg.drinkeg.domain.TastingNote foundNote = tastingNoteRepository.findById(noteId).orElseThrow(()
TastingNote foundNote = tastingNoteRepository.findById(noteId).orElseThrow(()
-> new GeneralException(ErrorStatus.TASTING_NOTE_NOT_FOUND)
);

Expand Down Expand Up @@ -194,14 +191,16 @@ public void updateTastingNote(Long noteId, TastingNoteUpdateRequestDTO tastingNo
public void deleteTastingNote(Long noteId, PrincipalDetail principalDetail) {

// 회원을 조회한다.
Member member = memberService.loadMemberByPrincipalDetail(principalDetail);
Member member = memberRepository.findByUsername(principalDetail.getUsername()).orElseThrow(
() -> new GeneralException(ErrorStatus.MEMBER_NOT_FOUND)
);

// noteId로 TastingNote를 찾는다.
com.drinkeg.drinkeg.domain.TastingNote foundNote = tastingNoteRepository.findById(noteId).orElseThrow(()
-> new GeneralException(ErrorStatus.TASTING_NOTE_NOT_FOUND)
// noteId로 TastingNote 를 찾는다.
TastingNote foundNote = tastingNoteRepository.findTastingNoteWithNoseById(noteId).orElseThrow(
() -> new GeneralException(ErrorStatus.TASTING_NOTE_NOT_FOUND)
);

// TastingNote의 Member가 요청한 Member와 같은지 확인한다.
// TastingNote 의 Member 가 요청한 Member 와 같은지 확인한다.
if(!foundNote.getMember().equals(member)) {
throw new GeneralException(ErrorStatus.TASTING_NOTE_FORBIDDEN);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public class WineServiceImpl implements WineService {
public List<SearchWineResponseDTO> searchWinesByName(String searchName, PrincipalDetail principalDetail) {

// 회원을 조회한다.
Member member = memberService.loadMemberByPrincipalDetail(principalDetail);
Member member = memberService.getMemberByUsername(principalDetail.getUsername());


// 검색한 와인 이름이 포함된 모든 와인을 찾는다 (LIKE '%검색어%').
Expand Down

0 comments on commit b9c6ef0

Please sign in to comment.