Spec
주요 지수 현황 요약
- 즐겨찾기된 지수의 성과 정보를 포함합니다.
- 성과는 종가를 기준으로 비교합니다.
Key Indices Summary
- Includes performance information for Favorited indices.
- Performance is compared based on the Closing Price.
Problem
This takes 1 + (N * 2) total database queries!
- Inefficient solution
- Get all favorited
IndexInfo+ get the Ids (1 query) - Then per the Id (N), get the most recent
IndexDataand the pastIndexDatafindRecentIndexData→ gets the top recentindexDatabased on yourindexInfoId(1 query)Optional<IndexData> current
findPastIndexData→ gets the target date’s (or the closest target date’s)indexData(1 query)Optional<IndexData>comparisonData
- Using those info, get the
PerformanceDto
- Get all favorited
@Override
public List<PerformanceDto> getFavPerformanceDto(PeriodType periodType) {
List<IndexInfoDto> favoriteInfoDtos = indexInfoRepository.getFavorites();
return favoriteInfoDtos.stream()
.map(indexInfoDto -> {
return getPerformanceDto(indexInfoDto, periodType);
})
.filter(Objects::nonNull)
.toList();
}
private PerformanceDto getPerformanceDto(IndexInfoDto i, PeriodType periodType) {
long indexInfoId = i.id();
IndexData current =
findRecentIndexData(indexInfoId)
.orElseThrow(() -> new NoSuchElementException("IndexData does not exist"));
LocalDate currentDate = current.getBaseDate();
Optional<IndexData> comparisonData =
switch (periodType) {
case DAILY -> findPastIndexData(indexInfoId, currentDate.minusDays(1));
case WEEKLY -> findPastIndexData(indexInfoId, currentDate.minusDays(7));
case MONTHLY -> findPastIndexData(indexInfoId, currentDate.minusMonths(1));
};
if (comparisonData.isEmpty()) {
return null;
}
// double currentPrice = current.closingPrice();
double currentPrice = current.getClosingPrice(); // 증가
double beforePrice = comparisonData.get().getClosingPrice();
double versus = currentPrice - beforePrice;
double fluctuationRate = versus / beforePrice * 100;
return new PerformanceDto(
indexInfoId,
i.indexClassification(),
i.indexName(),
versus,
fluctuationRate,
currentPrice,
beforePrice
);
}Solution
1 + N…
- Get the list of favorited
IndexInfo - Get the id list of #1 -
List<Long> indexInfoIdList - Get the most recent
IndexDatabased on theindexInfoIdList- 1 query- Make this into a map with key :
IndexInfoIdand value :IndexData Map<Long, IndexData> recentDataMap
- Make this into a map with key :
- Calculate performance for each favorite using the data we fetched (#3, #4)
Code (1차)
// 즐겨 찾기한 Inde @Override
public List<PerformanceDto> getFavPerformanceDto(PeriodType periodType) {
// 즐겨 찾기한 IndexData리스트 가져오기
// List<IndexInfo> favoriteInfos = indexInfoRepository.getFavorites();
List<IndexInfo> favoriteInfos = null;
// 위의 리스트 토대로 id 리스트 생성
List<Long> indexInfoIdList = favoriteInfos.stream()
.map(IndexInfo::getId)
.toList();
// id 리스트에 있는 IndexInfo당 가장 최신 IndexData 가져오기
// key: indexInfoId, value: IndexData
List<IndexData> recentIndexDataList = dashboardRepository.findRecentByIndexInfoIds(indexInfoIdList);
Map<Long, IndexData> recentIndexDataMap = recentIndexDataList.stream()
.collect(Collectors.toMap(i -> i.getIndexInfo().getId(), i -> i));
List<PerformanceDto> performanceDtoList = new ArrayList<>();
for (IndexInfo i : favoriteInfos) {
long indexInfoId = i.getId();
IndexData current = recentIndexDataMap.get(indexInfoId);
LocalDate currentDate = current.getBaseDate();
Optional<IndexData> comparisonData =
switch (periodType) {
case DAILY -> findPastIndexData(indexInfoId, currentDate.minusDays(1));
case WEEKLY -> findPastIndexData(indexInfoId, currentDate.minusDays(7));
case MONTHLY -> findPastIndexData(indexInfoId, currentDate.minusMonths(1));
};
if (comparisonData.isEmpty()) {
continue;
}
double currentPrice = current.getClosingPrice(); // 증가
double beforePrice = comparisonData.get().getClosingPrice();
double versus = currentPrice - beforePrice;
double fluctuationRate = 0.0;
if (beforePrice != 0) {
fluctuationRate = versus / beforePrice * 100;
}
performanceDtoList.add(new PerformanceDto(
indexInfoId,
i.getIndexClassification(),
i.getIndexName(),
versus,
fluctuationRate,
currentPrice,
beforePrice
)
);
}
return performanceDtoList;
}
===============================
private Optional<IndexData> findPastIndexData(long indexInfoId, LocalDate localDate) {
return dashboardRepository.findTopByIndexInfoIdAndBaseDateLessThanEqualOrderByBaseDateDesc(
indexInfoId, localDate);
}Database calls
public interface DashboardRepository extends JpaRepository<IndexData, Long> {
/**
* 특정 날짜(targetDate) 혹은 그 이전의 가장 최신 IndexData를 조회합니다. 주말이나 공휴일처럼 특정 날짜에 IndexData가 없는 경우를 처리하는 데
* 핵심적인 역할을 합니다.
*/Optional<IndexData> findTopByIndexInfoIdAndBaseDateLessThanEqualOrderByBaseDateDesc(
long indexInfoId, LocalDate baseDate);
/**
* 리스트에 있는 indexInfoId당 특정 indexInfoId에 해당하는 가장 최신 IndexData를 조회합니다.
*/@Query(
"SELECT d FROM IndexData d WHERE d.indexInfo.id IN :indexInfoIds AND d.baseDate = " +
"(SELECT MAX(d2.baseDate) FROM IndexData d2 WHERE d2.indexInfo.id = d.indexInfo.id)"
)
List<IndexData> findRecentByIndexInfoIds(@Param("indexInfoIds") List<Long> indexInfoIds);
...
}
findTopRecentByIndexInfoIdsSELECT d FROM IndexData d- This part starts by selecting the entire
IndexDataobject (aliased asd).
- This part starts by selecting the entire
WHERE d.indexInfo.id IN :indexInfoIds- 1st filter
- checks if the
indexInfo.idis inindexInfoIds(the list you passed)
(SELECT MAX(d2.baseDate) FROM IndexData d2 WHERE d2.indexInfo.id = d.indexInfo.id)- For each row from the 1st query, it performs a check:
(SELECT MAX(d2.baseDate) FROM IndexData d2: It runs a sub-query that asks, “For this specific index ID, what is the absolute latestbaseDate?”d2is just a 2nd alias forIndexData
- It only keeps the original row if its
baseDatematches that latest date.
- For each row from the 1st query, it performs a check:
findTopPastByIndexInfoIdsAndBaseDate