
QueryDSL 사용하기(SpringBoot 3.0) - QueryDSL 사용하기(4)Tech/Java & Spring2023. 8. 4. 05:34
Table of Contents
728x90
728x90
서론
드디어 아래의 쿼리를 QueryDSL로 뽑아내서 View시켜볼 수 있게 되었다.
select C.CHAMPION_ID, C.PRICE, ROUND((C.PRICE-CP.PRICE)/CP.PRICE*100) as PERCENT
from Champion C, (select CC.CHAMPION_ID, CC.PRICE
from ChampionPriceLog CC
where (CC.CHAMPION_ID, CC.CREATE_DATE) in(select CCC.CHAMPION_ID, MAX(CCC.CREATE_DATE) AS CREATE_DATE
from ChampionPriceLog CCC
group by CCC.CHAMPION_ID)
order by CC.CHAMPION_ID) as CP
where C.CHAMPION_ID = CP.CHAMPION_ID
order by C.CHAMPION_ID;
테스트
포스팅을 하게 만들었던 위의 쿼리를 사용하기 위해 QueryDSL을 사용해보기로 했다.
QChampion C = QChampion.champion;
QChampionPriceLog CPL = QChampionPriceLog.championPriceLog;
우선 필요한 QClass의 인스턴스를 생성한 뒤
List<Tuple> tupleList =
(List<Tuple>) jpaQueryFactory.select(C.name, CPL.price, C.price)
.from(CPL)
.join(C)
.on(CPL.champion_id.eq(C.id))
.where(
Expressions.list(CPL.champion_id, CPL.create_date)
.in(
jpaQueryFactory.select(
CPL.champion_id, CPL.create_date.max().as("create_date")
)
.from(CPL)
.groupBy(CPL.champion_id)
)
)
.orderBy(CPL.champion_id.asc())
.fetch();
조인을 실행한 결과를 Tuple형태로 반환시켰다.
Tuple은 QueryDSL에서 제공하는 컨테이너 클래스로, 쿼리 결과의 각 튜플을 나타낸다.
이를 원하는 형태로 View시키기 위해 아래와 같은 DTO객체를 사용했고, DTO에 원하는 정보를 tuple에서 뽑아서 List형태로 반환시켜 줄 계획이기 때문에 아래처럼 DTO와 반환 코드를 작성했다.
package com.example.lolchampionsinvestment.domain.champion;
import lombok.Data;
@Data
public class ChampionPriceDto {
private String name;
private int price;
private int percent;
public ChampionPriceDto(String name, int price, int percent) {
this.name = name;
this.price = price;
this.percent = percent;
}
}
List<ChampionPriceDto> championPriceDtoList = tupleList.stream()
.map(tuple -> {
String name = tuple.get(C.name);
int price = tuple.get(C.price);
int cplPrice = tuple.get(CPL.price);
int percent = Math.round((price - cplPrice) * 100 / price);
return new ChampionPriceDto(name, price, percent);
})
.collect(Collectors.toList());
select C.CHAMPION_ID, C.PRICE, ROUND((C.PRICE-CP.PRICE)/CP.PRICE*100) as PERCENT
from Champion C, (select CC.CHAMPION_ID, CC.PRICE
from ChampionPriceLog CC
where (CC.CHAMPION_ID, CC.CREATE_DATE) in(select CCC.CHAMPION_ID, MAX(CCC.CREATE_DATE) AS CREATE_DATE
from ChampionPriceLog CCC
group by CCC.CHAMPION_ID)
order by CC.CHAMPION_ID) as CP
where C.CHAMPION_ID = CP.CHAMPION_ID;
위의 쿼리를 QueryDSL로 수행하기 위한 테스트 코드 전체는 아래와 같다.
@SpringBootTest
@Transactional
public class ChampionPriceQueryRepositoryTest {
@PersistenceContext
EntityManager em;
JPAQueryFactory jpaQueryFactory;
@Autowired
ChamiponPriceQueryRepository championPriceQueryRepository;
@Autowired
ChampionDataParsingService championDataParsingService;
@BeforeEach
void init() {
jpaQueryFactory = new JPAQueryFactory(em);
}
@DisplayName("챔피언 별 가장 최근 가격을 가져온다.")
@Test
void getChampionsLatestPrice(){
// given
List<ChampionPriceDto> championPriceDtoList = championPriceQueryRepository.findAllLatestChampions();
// when // then
assertThat(championPriceDtoList.size()).isEqualTo(164);
assertThat(championPriceDtoList)
.extracting("name", "price", "percent")
.containsAnyOf(
tuple("아트록스", 7000, -36),
tuple("아리", 2000, 0),
tuple("카이사", 8000, 0)
);
}
}
테스트를 위해, 아트록스 데이터는 11000원을 입력해두었다.
원하는 값인 현재 금액 : 7000, 금일 등락 퍼센트인 -36%가 잘 반환된 모습이다.
적용
테스트코드를 적용시킨 Repository를 만들고
아래의 서비스 객체를 만든 뒤
@Service
@RequiredArgsConstructor
@Transactional
public class ChampionService {
private final ChamiponPriceQueryRepository championPriceQueryRepository;
public List<List<ChampionPriceDto>> getAllLatestChampions() {
List<ChampionPriceDto> list = championPriceQueryRepository.findAllLatestChampions();
int listSize = list.size();
List<List<ChampionPriceDto>> groupingList = new ArrayList<>();
for(int i = 0; i < listSize; i+=8) {
int endIdx = Math.min(i + 8, listSize);
groupingList.add(list.subList(i, endIdx));
}
return groupingList;
}
}
Controller를 통해 View를 시켜 보았다.
@RestController
public class MainViewController {
@Autowired
ChampionService championService;
@GetMapping("/")
public ModelAndView mainView() {
ModelAndView mv = new ModelAndView();
List<List<ChampionPriceDto>> groupingList = championService.getAllLatestChampions();
mv.addObject("groupingList", groupingList);
mv.setViewName("main/main.html");
return mv;
}
}
8개씩 그룹핑 한 이유는, View를 그렇게 하기 위함이었는데
일단 원하는 대로 잘 들어오긴 했고, 모든 데이터가 다 들어오기 때문에, 메인 화면에 보여주는 것이다 보니 등락폭이 큰 녀석들만 추려서 View시키던, 페이징을 시키는 방향으로 보수작업을 할 것 같다.
관련 포스팅
728x90
300x250
@mag1c :: 꾸준히 재밌게
2023.04 ~ 백엔드 개발자의 기록
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!