프로젝트 진행 중, 페이징 쿼리가 연속적으로 발생하는 것을 발견하였다. 쿼리가 한번만 날라갔으면 어떨까.. 싶어서 리팩토링을 하게 되었다.
페이징을 하기 전, 프로젝트의 흐름은 다음과 같다.
페이징은 위 과정을 거쳤다 가정하고 다음 부분에서 일어난다.
userHistory를 꺼내와서 Motion 이라는 테이블에 담겨있는 Url(해당 테이블은 미리 생성되어있고, 조회만 가능하다)에서 userHistory가 포함된 Url을 가져와 클라이언트에 반환한다.
public Page<ViewResponse> createViewResponse(Long userId, Pageable pageable) {
User user = getUserById(userId);
List<String> userHistories = getUserUserHistory(user);
List<String> allGifs = new ArrayList<>();
List<String> allFbxs = new ArrayList<>();
for (String userHistory : userHistories) {
Page<String> gifPage = motionRepository.findGifByMotionContaining(userHistory, pageable);
Page<String> fbxPage = motionRepository.findFbxByMotionContaining(userHistory, pageable);
allGifs.addAll(gifPage.getContent());
allFbxs.addAll(fbxPage.getContent());
});
}
ViewResponse viewResponse = ViewResponse.fromList(allGifs, allFbxs);
return new PageImpl<>(Collections.singletonList(viewResponse), pageable, allGifs.size());
}
// MotionRepository.java
@Query("select m.motionGif from Motion m where m.motionGif like %:message%")
Page<String> findGifByMotionContaining(@Param("message") String message, Pageable pageable);
@Query("select m.motionFbx from Motion m where m.motionFbx like %:message%")
Page<String> findFbxByMotionContaining(@Param("message") String message, Pageable pageable);
이때, 페이징을 할때 쿼리가 3개가 날라간다.
Hibernate:
select
user0_.id as id1_4_0_,
user0_.created_date as created_2_4_0_,
user0_.modified_date as modified3_4_0_,
user0_.email as email4_4_0_,
user0_.email_verified as email_ve5_4_0_,
user0_.image_url as image_ur6_4_0_,
user0_.name as name7_4_0_,
user0_.password as password8_4_0_,
user0_.provider as provider9_4_0_,
user0_.provider_id as provide10_4_0_,
user0_.role as role11_4_0_,
user0_.user_group as user_gr12_4_0_,
user0_.user_history as user_hi13_4_0_
from
user user0_
where
user0_.id=?
Hibernate:
select
motion0_.motion_gif as col_0_0_
from
motion motion0_
where
motion0_.motion_gif like concat('%', ?, '%') limit ?
Hibernate:
select
motion0_.motion_fbx as col_0_0_
from
motion motion0_
where
motion0_.motion_fbx like concat('%', ?, '%') limit ?
- 유저를 검색하는 쿼리
- gif를 검색하는 쿼리
- fbx를 검색하는 쿼리
gif와 fbx 두개를 동시에 검색하게 하고싶었다. 겸사겸사 user를 조회하면서 동시에 gif와 fbx 검색을 가능하게 하고싶었다. 우선, gif와 fbx를 하나의 쿼리에서 동작하게끔 바꾸어보았다.
public Page<ViewResponse> createViewResponse(Long userId, Pageable pageable) {
User user = getUserById(userId);
List<String> userHistories = getUserUserHistory(user);
List<String> allGifs = new ArrayList<>();
List<String> allFbxs = new ArrayList<>();
for (String userHistory : userHistories) {
Page<Motion> motionPage = motionRepository.findByMotionContaining(userHistory, pageable);
motionPage.getContent().stream().forEach(motion -> {
allGifs.add(motion.getMotionGif());
allFbxs.add(motion.getMotionFbx());
});
}
ViewResponse viewResponse = ViewResponse.fromList(allGifs, allFbxs);
return new PageImpl<>(Collections.singletonList(viewResponse), pageable, allGifs.size());
}
// MotionRepository.java
@Query("select m from Motion m where m.motionGif like %:userHistory% or m.motionFbx like %:userHistory%")
Page<Motion> findByMotionContaining(@Param("userHistory") String userHistory, Pageable pageable);
기존에는 String으로 따로 가져왔었는데 Motion 객체에 gif, fbx의 정보를 함께 담아둔다. 그리고 페이징된 Motion 객체의 gif와 fbx 정보를 각각의 리스트에 저장한다.
이 작업을 수행한 결과 발생한 쿼리가 2개로 줄었다!
Hibernate:
select
user0_.id as id1_4_0_,
user0_.created_date as created_2_4_0_,
user0_.modified_date as modified3_4_0_,
user0_.email as email4_4_0_,
user0_.email_verified as email_ve5_4_0_,
user0_.image_url as image_ur6_4_0_,
user0_.name as name7_4_0_,
user0_.password as password8_4_0_,
user0_.provider as provider9_4_0_,
user0_.provider_id as provide10_4_0_,
user0_.role as role11_4_0_,
user0_.user_group as user_gr12_4_0_,
user0_.user_history as user_hi13_4_0_
from
user user0_
where
user0_.id=?
Hibernate:
select
motion0_.id as id1_2_,
motion0_.motion_fbx as motion_f2_2_,
motion0_.motion_gif as motion_g3_2_
from
motion motion0_
where
motion0_.motion_gif like concat('%', ?, '%')
or motion0_.motion_fbx like concat('%', ?, '%') limit ?
이후 한번의 쿼리로 전부를 가져오는 작업을 진행했는데 막상 완성해보니 성능은 좋아질 순 있어도 코드의 가독성이 너무 안좋아지는 것 같았다.
둘의 정보를 가진 DTO를 만들거나 쿼리를 복잡하게 짜거나..하면 가져올 수는 있었으나, 혼자 작업해도 이 코드는 도저히 못쓸 것같아서 기각했다.
서로 직접적인 연관관계도 없다보니 일단은 2개로 멈추기로 했다. 물론, 내가 생각한 방법이 최선은 아닐 것이고 분명 어떤 방법이 있을 것 같다는 생각도 든다.