개요
Sensory (촉각 정보)
테이블 | 설명 |
sensory | 술의 촉각 정보를 정의하는 테이블 |
sensory_level | 술의 촉각 정보를 점수화하여 정량적으로 평가한 테이블 |
alcohol_type | 술의 종류(주종)를 정의하는 테이블 |
alcohol_type_sensory | 특정 주종과 촉각 정보를 연결하는 테이블 |
tasting_note | 술의 알코올 도수, 양조장, 내용, 평점, 색상, 주관 평가 등을 저장하는 시음 노트 테이블 |
tasting_note_sensory_level | 시음 노트에 저장된 촉각 정보와 그에 대한 점수 및 설명을 기록하는 테이블 |
[트러블 슈팅] 조회 로직 개선하기
문제 상황 : 반복적인 조회를 진행하게 되어 성능 저하가 발생한다.
시음 노트 작성 시, 사용자는 각 주종별로 다른 촉각 정보를 입력하게 됩니다. 주종에 따라 조회되는 촉각 정보의 종류가 다르고, 각 촉각 타입에 따라 점수(score)와 설명(description)도 다르게 나타납니다. 여기서 주종별로 촉각 정보를 조회할 때 각 sensoryId(촉각 정보 고유 번호)별로 여러 개의 SensoryLevel을 조회할 경우 sensoryId별로 반복적인 조회를 진행하게 되어 성능 저하를 유발하게 됩니다.
해결 방안 : Map을 활용하여 데이터를 그룹화한다.
Map을 활용하여 Sensory 정보를 한 번에 조회하고, 각 sensoryId를 기준으로 Level 데이터를 그룹화하여 성능을 최적화했습니다.
@Reader
@RequiredArgsConstructor
public class AlcoholTypeSensoryReader {
private final AlcoholTypeSensoryJpaRepository alcoholTypeSensoryJpaRepository;
private final AlcoholTypeSensoryQueryRepository alcoholTypeSensoryQueryRepository;
// ...
public List<SensoryLevelInfo> getAllSensoryLevelInfoByAlcoholTypeId(Long alcoholTypeId) {
return alcoholTypeSensoryQueryRepository.findAllInfoByAlcoholTypeId(alcoholTypeId);
}
}
@Repository
@RequiredArgsConstructor
public class AlcoholTypeSensoryQueryRepository {
private final JPAQueryFactory jpaQueryFactory;
QAlcoholTypeSensory alcoholTypeSensory = QAlcoholTypeSensory.alcoholTypeSensory;
QSensory sensory = QSensory.sensory;
QSensoryLevel sensoryLevel = QSensoryLevel.sensoryLevel;
public List<SensoryLevelInfo> findAllInfoByAlcoholTypeId(Long alcoholTypeId) {
Map<Long, SensoryLevelInfo> sensoryMap = new HashMap<>();
List<Tuple> sensoryTuples = jpaQueryFactory
.select(
sensory.id,
sensory.name,
sensoryLevel.id,
sensoryLevel.score,
sensoryLevel.description
)
.from(alcoholTypeSensory)
.innerJoin(sensory).on(alcoholTypeSensory.sensory.eq(sensory))
.innerJoin(sensoryLevel).on(sensory.eq(sensoryLevel.sensory))
.where(
eqAlcoholTypeId(alcoholTypeSensory.alcoholType, alcoholTypeId),
isUsed(alcoholTypeSensory)
)
.orderBy(
sensory.id.asc(),
sensoryLevel.score.asc()
)
.fetch();
sensoryTuples.forEach(s -> {
Long sensoryId = s.get(this.sensory.id);
SensoryLevelInfo sensoryLevelInfo = sensoryMap.computeIfAbsent(sensoryId,
id -> new SensoryLevelInfo(
new SensoryInfo(
sensoryId,
s.get(this.sensory.name)
),
new ArrayList<>()
));
Level level = new Level(
s.get(this.sensoryLevel.id),
s.get(this.sensoryLevel.score),
s.get(this.sensoryLevel.description)
);
sensoryLevelInfo.levels().add(level);
});
return new ArrayList<>(sensoryMap.values());
}
// ...
}
1. Sensory 정보와 그에 해당하는 Level 정보를 한 번의 쿼리로 모두 가져옵니다.
2. 가져온 데이터 sensoryTuples를 sensoryId별로 그룹화하여 Map에 저장한 후, 이를 통해 각 sensoryId에 해당하는 Level 정보들을 리스트에 추가합니다.
("computeIfAbsent()" 메소드는 Java의 Map 인터페이스에 포함된 메소드 중 하나로, 특정 키에 대해 값이 없을 경우 계산된 값을 맵에 저장하고, 그 값을 반환하는 역할을 합니다.)
3. 이후 Map의 "values()" 메소드를 통해 List<SensoryLevelInfo>를 가져옵니다.
이를 통해 D별로 반복 조회하는 로직을 제거하고 성능을 최적화했습니다. 또한 데이터를 그룹화하여 가독성을 높였습니다.
감사합니다. : )
'PROJECT > 주라벨' 카테고리의 다른 글
[주라벨] @FeignClient 유연한 소셜 로그인 구현 (with. 카카오) (0) | 2024.08.11 |
---|