project list 조회 기능 1차 refactoring query dsl 도입

권희·2025년 9월 5일

졸업작품

목록 보기
2/7

목차

  1. project list 기능 조회 / 관련 db 설명
  2. 이전 native query 형식
  3. query dsl도입
  4. 마무리

1. project list 조회 기능 / 관련 db 설명

펀딩 사이트에서 펀딩을 위한 정보를 담기 위한 table project entity의 정보와 다른 table들의 정보를 한 눈에 확인하기 위한 project list api이다

project list에 관련된 table

  • project
  • user (생성자 정보)
  • img (대표 이미지)
  • project tag (tag값들)
  • project view (조회수)
  • like (추천 수)
  • funding (펀딩 정보, compelete rate 계산)

2. 이전 native query 형식

처음 project list 조회 기능은 한개의 api에서만 사용했고, 필터 기능또한 tag, 제목 검색 2가지 밖에 존재하지 않았기 때문에 native query로 충분히 구현가능 하다고 생각했었다

WITH filtered_projects AS (
  SELECT distinct p.*
  FROM project p 
  join project_tag pt on p.id = pt.project_id
  where (조건 %s) 
),
project_count AS (
  SELECT COUNT(*) AS total_count
  FROM filtered_projects
)
SELECT fp.id, fp.title, fp.created_at, fp.expired ,
  (CAST((SELECT SUM(f.free_price + op.price) FROM funding f left join `option` op on op.id = f.option_id WHERE f.project_id = fp.id) AS DOUBLE) * 100.0 / fp.goal) AS completion_rate,
  (SELECT COUNT(DISTINCT r.user_id) FROM `like` r WHERE r.project_id = fp.id) AS like_count, 
  (SELECT COUNT(f.user_id) FROM funding f WHERE f.project_id = fp.id) AS user_count, 
  (SELECT i.uri FROM img i WHERE i.id = fp.img_id) AS title_img, 
  (cast(%sas signed) )as is_like, 
  (select count(pv.id) from project_view as pv where pv.project_id = fp.id) as view_count ,
  pc.total_count
FROM filtered_projects fp
CROSS JOIN project_count pc
order by 
limit 

이후 where, order by, limit값을 java 코드로 작성하여 String 형식으로 넣어주는 방법을 사용했다

3. query dsl도입

이후 funding table 다양화와 다른 col 추가를 위해서는 project list 조회 함수가 유지보수 / 기능 확장이 가능하게 수정해둘 필요가 있었다

고민한 해결 방법

  1. native query를 기능별 함수로 분리
    query dsl또한 결국 java로 native query를 자성해주는 도구일 뿐이다
    completet rate, user count, like_count등등 모두 select project list user repo, like repo, funding repo로 분리하여 native query를 관리한다
  • 장점 : 확실한 성능 보장
  • 단점 : 유지 보수 / 확정에 어려움이 있을 수 있음
  1. query dsl 도입
    native query를 직접 관리하지 말고 java 코드를 통해 관리한다
  • 장점 : 유지 보수 / 확장성이 좋다
  • 단점 : 구현에 오래 걸린다, mysql native함수들의 사용에 제한이 걸린다

예전 부터 고민했던 동적 query 작성을 위한 orm 중 query dsl을 선택하여 도입하게 되었다

발생 문제

  1. Expression?
    query dsl에는 정말 다양한 type들이 존재했다 코드를 작성하기 전에 최소한 Expression, JPAQuery 라는 class만 알고 있었어도 구현하는데 그리 고생하지는 않았을 것이다
    간단한 type들만 정리하겠다
JPAQuery JPAQueryFactory.select(Expression args...)
EntityPathBase<ProjectEntity> : query dsl실행 시 entity마다 자동 생성되는 class (entity의 별명으로 사용됨)
BooleanExpression : Expression type중 한가지, where 조건문에 사용됨
OrderSpecifier : order by절에서 사용됨
  1. select에 있는 조회결과를 바로 order by에서 사용할 수 없다
-- 불가능
select(
  project.title, 
  ( select count(*) from like where like.project.eq(project) ) as like_count
)
from project
order by like_count

select에서 조회한 값을 사용한 정렬이 불가능하다 때문에 order by에서 똑같은 sub query를 실행해야한다

  1. native query 사용 불가능
    당연하지만 위의 native query에서 사용한 WITH함수는 방식은 사용할 수 없다
    2번과 3번이 곂치면서 많은 sub query가 발생하게 되었다

4. 마무리

구현한 내용이다 (+약간의 생략)

.select(Projections.constructor(ResponseProjectListDto.class,
  projectEntity.id,
  projectEntity.title,
  (생략),
  projectEntity.titleImg,
  completeRate(projectEntity), // completeRate
  likeCount(projectEntity), // like count
  fundingUserCount(projectEntity), // user_count
  isLike(projectEntity, dto.getUserId()), // is_like
  viewCount(projectEntity), // view_count
  totalCount(projectEntity, dto) // total _ select count :: totalCount
))
.from(projectEntity)
.leftJoin(생략)
.where(
  where(projectEntity, projectTagEntity, dto.getUserId(), dto.getTagIds(), dto.getSearch())
)
.orderBy(
  orderByType(projectEntity, ProjectOrderType.getType(dto.getOrder()), dto.getDesc()),
  projectEntity.createdAt.desc()
)
.limit(dto.getPageCount())
.offset(dto.getPageCount() * dto.getPage())
.fetch();

충분히 native query보다는 유지 보수 가능한 코드가 되었다 하지만 query 성능 자체는 크게 감소하게 된 것 같다
일단 project list 조회 기능 함수의 refactoring은 이것으로 마치고 추후 funding table 세분화, 다른 col이 추가되었을 때 이전 native query로 가능한지 여부에 따라 roll back을 결정해야 할 것 같다

profile
나의 개발 기록

0개의 댓글