비지니스 로직을 작성하며 영속성, JOIN, N+1 등 다양한 문제들을 고민하며 작성했지만, 프로젝트 후반부가 되면서 전보다 인사이트가 조금 생겼다.
작성한 코드들을 다시 셀프 리뷰 하면서 더 좋은 방향에 대해 고민하고, 궁금했지만 넘어갔던 것들에 대해 다시 짚어보려고한다.
service : getDetail
public DetailCahootsDto getDetail(Long cahootsId, Long userId) {
DetailCahootsDto detailCahootsDto = vacationRepository.getVacationDetail(cahootsId).checkNull();
List<Interest> interests = interestRepository.findByVacation(vacationRepository.getReferenceById(cahootsId));
detailCahootsDto.setInterestCount(interests.size());
Boolean isInterest = (userId != null ? interests.stream().map(Interest::getUser).map(User::getId).anyMatch(id -> id.equals(userId)) : false);
detailCahootsDto.setIsInterest(isInterest);
detailCahootsDto.setImages(getImageUrls(cahootsId));
return detailCahootsDto;
}
repository
질문해보기
실제로 쿼리는 총 몇개가 던져 졌는가?
Hibernate:
/* select
vacation.id,
vacation.title,
vacation.location,
vacation.country,
vacation.status,
vacation.theme.themeLocation,
vacation.theme.themeBuilding,
vacation.plan.expectedTotalCost,
vacation.plan.expectedMonth,
vacation.shortDescription,
vacation.description,
vacation.expectedRateOfReturn,
vacation.stock.price as stockPrice,
vacation.stock.num as stockNum,
vacation.stockPeriod.start as stockStart,
vacation.stockPeriod.end as stockEnd,
(coalesce(sum(contestParticipation.stocks),
?1) * ?2 / vacation.stock.num) as competitionRate
from
Vacation vacation
left join
vacation.historyList as contestParticipation
where
vacation.id = ?3 */
Hibernate:
/* select
generatedAlias0
from
Interest as generatedAlias0
where
generatedAlias0.vacation=:param0 */
Hibernate:
/* select
picture.url
from
Picture picture
where
picture.vacation.id = ?1 */interests.stream().map(Interest::getUser) 부분에 왜 추가 쿼리는 발생하지 않을까?
for (Interest interest : interests) {
System.out.println(interest.getVacation());
System.out.println(interest.getUser());
}for (Interest interest : interests) {
System.out.println(interest.getVacation().getId());
System.out.println(interest.getUser().getId());
}stream에서 User::getId 대신 다른 column (ex. email)을 가져오면 어떻게 되는가?
List<Interest> interests = interestRepository.findByVacation(vacationRepository.getReferenceById(cahootsId));
for (Interest interest: interests) {
System.out.println(interest.getUser().getEmail());
}Hibernate:
select
user0_.id as id1_8_0_,
user0_.cash as cash2_8_0_,
user0_.email as email3_8_0_,
user0_.nickname as nickname4_8_0_,
user0_.provider_id as provider5_8_0_,
user0_.provider_type as provider6_8_0_,
user0_.ranks as ranks7_8_0_,
user0_.refresh_token as refresh_8_8_0_,
user0_.role as role9_8_0_
from
user user0_
where
user0_.id=?
A@gmail.com
Hibernate:
select
user0_.id as id1_8_0_,
user0_.cash as cash2_8_0_,
user0_.email as email3_8_0_,
user0_.nickname as nickname4_8_0_,
user0_.provider_id as provider5_8_0_,
user0_.provider_type as provider6_8_0_,
user0_.ranks as ranks7_8_0_,
user0_.refresh_token as refresh_8_8_0_,
user0_.role as role9_8_0_
from
user user0_
where
user0_.id=?
B@gmail.com
Hibernate:
select
user0_.id as id1_8_0_,
user0_.cash as cash2_8_0_,
user0_.email as email3_8_0_,
user0_.nickname as nickname4_8_0_,
user0_.provider_id as provider5_8_0_,
user0_.provider_type as provider6_8_0_,
user0_.ranks as ranks7_8_0_,
user0_.refresh_token as refresh_8_8_0_,
user0_.role as role9_8_0_
from
user user0_
where
user0_.id=?
C@gmail.com3개의 쿼리를 더 줄일 수 없는가?

조건 : 1초당 5번 요청 x 60초
왼쪽 사진) 기존, 오른쪽 사진) 개선 쿼리


service : participate
@Transactional
public void participate(Long cahootsId, Integer stocks, User user){
if (stocks > 10000) { // 주식 너무 많으면 reject
throw new ApiException(ErrorCode.INVALID_PARAMETER);
}
Vacation vacation = vacationRepository.findByIdAndStatus(cahootsId, VacationStatusType.CAHOOTS_ONGOING).orElseThrow(()-> new ApiException(ErrorCode.VACATION_NOT_FOUND));
Long cash = user.getCash();
Long stockTotalPrice = vacation.getStock().getPrice() * stocks;
verifyUserCash(cash, stockTotalPrice);
// 사용자 정보에서 잔액 차감 + 공모 참여 현황에 추가
subtractUserCash(user, stockTotalPrice);
ContestParticipation contestParticipation = ContestParticipation.builder().user(user).vacation(vacation).stocks(stocks).build();
contestParticipationRepository.save(contestParticipation);
}
private void verifyUserCash(Long userCash, Long stockTotalPrice){
if(userCash < stockTotalPrice){ // 공모 정보 가져올 때 진행중이 맞는지 체크하고 잔액이 모자라면 에러
throw new ApiException(ErrorCode.USER_LACK_OF_CACHE);
}
}
private void subtractUserCash(User user, Long stockTotalPrice){
Long leftCash = user.getCash() - stockTotalPrice;
user.setCash(leftCash);
userRepository.updateCash(user.getId(), user.getCash());
}
repository
질문 해보기
transaction으로 처리한 이유는?
participate를 호출하면 총 몇 번의 쿼리가 발생하는가?
select
vacation0_.id as id1_9_,
vacation0_.created_at as created_2_9_,
vacation0_.updated_at as updated_3_9_,
vacation0_.country as country4_9_,
vacation0_.description as descript5_9_,
vacation0_.expected_rate_of_return as expected6_9_,
vacation0_.location as location7_9_,
vacation0_.expected_month as expected8_9_,
vacation0_.expected_total_cost as expected9_9_,
vacation0_.short_description as short_d10_9_,
vacation0_.status as status11_9_,
vacation0_.num as num12_9_,
vacation0_.price as price13_9_,
vacation0_.end as end14_9_,
vacation0_.start as start15_9_,
vacation0_.theme_building as theme_b16_9_,
vacation0_.theme_location as theme_l17_9_,
vacation0_.title as title18_9_,
vacation0_.user_id as user_id19_9_
from
vacation vacation0_
where
vacation0_.id=?
and vacation0_.status=?
---------
update
user
set
cash=?
where
id=?
------------
insert
into
contest_participation
(created_at, updated_at, status, stocks, user_id, cahoots_id)
values
(?, ?, ?, ?, ?, ?)parameter로 넘어온 user는 영속성을 가지는가?

그러면 따로 update query가 없으면 user는 dirty checking에 걸리지 않는가?
select
vacation0_.id as id1_9_,
vacation0_.created_at as created_2_9_,
vacation0_.updated_at as updated_3_9_,
vacation0_.country as country4_9_,
vacation0_.description as descript5_9_,
vacation0_.expected_rate_of_return as expected6_9_,
vacation0_.location as location7_9_,
vacation0_.expected_month as expected8_9_,
vacation0_.expected_total_cost as expected9_9_,
vacation0_.short_description as short_d10_9_,
vacation0_.status as status11_9_,
vacation0_.num as num12_9_,
vacation0_.price as price13_9_,
vacation0_.end as end14_9_,
vacation0_.start as start15_9_,
vacation0_.theme_building as theme_b16_9_,
vacation0_.theme_location as theme_l17_9_,
vacation0_.title as title18_9_,
vacation0_.user_id as user_id19_9_
from
vacation vacation0_
where
vacation0_.id=?
and vacation0_.status=?
-----------
insert
into
contest_participation
(created_at, updated_at, status, stocks, user_id, cahoots_id)
values
(?, ?, ?, ?, ?, ?)수정 시에 save를 호출하지 않고 updateCash를 따로 구현한 이유는 무엇인가?
select
vacation0_.id as id1_9_,
vacation0_.created_at as created_2_9_,
vacation0_.updated_at as updated_3_9_,
vacation0_.country as country4_9_,
vacation0_.description as descript5_9_,
vacation0_.expected_rate_of_return as expected6_9_,
vacation0_.location as location7_9_,
vacation0_.expected_month as expected8_9_,
vacation0_.expected_total_cost as expected9_9_,
vacation0_.short_description as short_d10_9_,
vacation0_.status as status11_9_,
vacation0_.num as num12_9_,
vacation0_.price as price13_9_,
vacation0_.end as end14_9_,
vacation0_.start as start15_9_,
vacation0_.theme_building as theme_b16_9_,
vacation0_.theme_location as theme_l17_9_,
vacation0_.title as title18_9_,
vacation0_.user_id as user_id19_9_
from
vacation vacation0_
where
vacation0_.id=?
and vacation0_.status=?
------
# user select
select
user0_.id as id1_8_0_,
user0_.cash as cash2_8_0_,
user0_.email as email3_8_0_,
user0_.nickname as nickname4_8_0_,
user0_.provider_id as provider5_8_0_,
user0_.provider_type as provider6_8_0_,
user0_.ranks as ranks7_8_0_,
user0_.refresh_token as refresh_8_8_0_,
user0_.role as role9_8_0_
from
user user0_
where
user0_.id=?
------
insert
into
contest_participation
(created_at, updated_at, status, stocks, user_id, cahoots_id)
values
(?, ?, ?, ?, ?, ?)
-------
# user update
update
user
set
cash=?
where
id=?