본문 바로가기
Today I Learned 2024. 7. 2.

(24.07.02)[12주차] QueryDSL의 Wildcard 와 Count

count를 사용하여 총 갯수를 구하려는 쿼리문을 QueryDSL로 작성을 해야할 필요가 있다.

하지만, 어떤 상황에 따라서는 Join 에 따라서 count가 실행되지 않을 수 있다.

 


 

QueryDSL 의 Wildcard 사용 이슈

public ProfileResponseDto getUserProfile(String userName) {

        QUser qUser = QUser.user;
        QLike qLike = QLike.like;

        ...
	Long aptLikesCount = jpaQueryFactory.select(Wildcard.count)
                .from(qLike)
                .leftJoin(qLike.user, qUser).fetchJoin()
                .where(qLike.user.userName.eq(user.getUserName())
                        .and(qLike.apart.isNotNull()))
                .fetchOne();
                
        ...

이슈

구현 목적

  • User정보와 1:N 관계를 맺고 있고, 해당 User의 Apart 게시글과 Qna 댓글의 아이디를 필드로 각각 연관관계를 가지고 있는, Like 좋아요 엔터티가 있는 코드 = 사용자가 로그인 후, 게시글과 댓글에 좋아요를 요청할 때 마다, DB에 해당 좋아요가 어느 게시물 또는 어느 좋아요에 누가 추가를 했는지 ID를 저장하는 로직
  • 정보를 가지고 있는 Like 엔터티에서, 해당 사용자의 Username이 Apart에 관하여 좋아요 누른 정보를 취합하여 몇개의 좋아요를 눌렀는지 조회를 해야하는 것이 목적
    • 로그인을 한 후, Header에 담겨있는 Access Token으로 사용자 인가 인증을 맞췄기 때문에, Controller단에서 UserDetails에서 User 객체를 받아오기 때문에, 비밀번호, DB의 PK인 ID 등이 아닌 userName 만받아 올 수 있어 userName으로 찾는것이 일단 목적

구체적 이슈

  • Like엔터티에는 User 엔터티가 user_id로 매핑되어 연관관계가 설정되어 있기 때문에,  Likes와 Users 테이블을 JOIN 한 테이블에서 user_name을 WHERE 조건을 걸고 Wildecard를 사용해서 그 데이터의 갯수를 알고자 함
    • 따라서, 한번에 모든 데이터를 가지고와서 JOIN 시키기위해서 fetchJoin() 메서드를 사용한 후, Wildcard.count 를 사용한다면, 클라이언트에게 500 서버 에러코드로 응답

원인

  • QueryDSL의 count 관련 쿼리들의 특징을 이해를 하지 못한 상태
    • 원래는 한번에 FETCH JOIN 으로 EAGER 타입으로 모든 연관관계의 데이터들을 불러와서 그 데이터들의 갯수를 세는 것이 의도였으나, count 쿼리들은 그렇게 작동을 하지 X
  • fetchJoin 은  Hibernate 가 연관 엔터티 데이터 모두를 한번에 로드하라고 지시를 내림 (FetchType도 무시) -> count는 이러한 로드가 필요하지 않고 곧바로 레코드 수를 세려고 시작함 -> Hibernate는 연속적인 지시를 계속해서 추가적으로 수행하면서 Server에 500 에러 코드를 날리게됨  

해결

Long aptLikesCount = jpaQueryFactory.select(Wildcard.count)
                .from(qLike)
                .leftJoin(qLike.user, qUser)
                .where(qLike.user.userName.eq(user.getUserName())
                        .and(qLike.apart.isNotNull()))
                .fetchOne();
  • count 관련 쿼리들을 JPA 환경에서 사용을 하려면 EAGER Loading하지 않는 상태에서 count 를 시켜줘야하므로, fetchJoin 자체를 쓸 필요가 X
    • 그냥 JOIN 만 하면 데이터 즉시 로딩 작업 없이, 전체적인 테이블의 row 데이터 값의 갯수만 세서 Long타입으로 반환
  • 그냥, fetchJoin() 없이 Join 관련 쿼리만 사용하면 됨!
    • 매우 단순한 해결이지만, 이해가 부족할 수 있다는 점
    • SQL 의 접근과 Hibernate가 지시를 수행한다는 점에 있어서 다른 부분이므로 인지를 하고 있어야

Wildcard

  • QueryDSL에서만 사용하는 특별한 표현
  • 카운트 쿼리에서 사용할 수 있는 공통적인 상수
    • 즉, QueryDSL 처럼 사용할 수 있는 어떠한 상수라고 이해할 수 있음

Wildcard.all

QEmployee employee = QEmployee.employee;
List<Employee> employees = queryFactory.select(Wildcard.all)
    .from(employee)
    .fetch();
  • 모든 데이터를 선택한다는 와일드카드
    • 사실상 그냥 조회하고 싶은 엔터티를 넣으면 됨
  • 명시적으로 써주는걸 많이 사용함

Wildcard.count

QEmployee employee = QEmployee.employee;
long employeeCount = queryFactory.select(Wildcard.count)
    .from(employee)
    .fetchOne();
  • 모든 데이터의 갯수 = row 갯수를 Long 타입으로 반환
    • fetchOne() 을 반드시 사용을 해줘야함
  • count() 와 동일하지만 명시적으로 모든 결과 값들의 갯수를 세갰다는 것을 보여줄 수 있음