Spring Boot에서 Querydsl 사용

2024. 12. 4. 13:11·Spring

이 글에는 Spring Boot 프로젝트에서 왜 Querydsl을 사용했는지와 어떻게 QueryDsl을 사용했는지에 대해 작성되어 있습니다.

 

저는 프로젝트를 진행하던 중 입력받은 정보를 바탕으로 내 맞춤 리스트를 보여주는 API가 필요해졌습니다.

사용자에게 맞춤 리스트를 보여주기 위해서는 입력받는 유저 정보에 따라 사용자들에게 다른 맞춤형 리스트를 보여줘야 했고, 그러기 위해서는 사용자마다 다른 쿼리를 사용해야 했습니다.

이렇게 사용자마다 쿼리 내용이 바뀌어야 할 때 사용되는 것이 바로 동적쿼리입니다.

 

동적쿼리를 사용하는 것에는 다양한 장점이 있는데 다음과 같습니다.

  • 다양한 사용자 요청에 처리 가능
  • 코드의 중복을 줄일 수 있음
  • 가독성과 유지보수 향상
  • 성능 최적화(불필요한 조건만 포함)
  • 확장성(조건을 추가하고 싶을 때 기존 코드를 건드리지 않고 추가할 수 있음)
  • 테스트 용이 

저는 이 중에서 다양한 사용자 요청에 처리 가능하고 확장성에서의 이점을 얻기 위해서 동적쿼리를 사용했습니다. 다른 장점도 있지만 앞서 말한 두 장점이 제게 가장 필요했기 때문에 두 기능에 초점을 맞췄습니다.

 

동적쿼리를 사용하기 위해서는 다음과 같은 방법들을 이용할 수 있습니다.

Querydsl

스프링에서 동적쿼리를 사용할 때 가장 유명한 방법 중 한 가지입니다. Q타입 엔티티를 이용해서 동적쿼리를 작성하고 쿼리를 sql로 작성하는 것이 아닌 자바 코드를 통해서 작성할 수 있다는 장점이 있습니다. 컴파일 시점에 오류를 발견할 수 있고, 모듈화가 쉽습니다. 개인적으로 BooleanExpression을 사용하면 가독성과 코드 재사용성이 정말 좋다고 생각합니다. 다만, 초반 설정을 할 때 조금 복잡하고 다양한 에러가 발생할 수 있다는 단점이 있습니다.

 

저의 경우도 Q파일이 생성되지 않는 오류가 발생해서 gradle을 clean 하고 build 하는 등 여러 가지 방법을 사용했지만 결국 해결하지 못하고 프로젝트 폴더 자체를 삭제하고 github에서 다시 클론 해서 설정을 추가하였더니 해결됐습니다. 중간에 자바 버전이 바뀌면서 자바를 관리하는 회사가 바뀌면서 javax에서 jakarta가 되었기 때문에 querydsl 설정에서도 이 부분을 추가해주어야 합니다.

다음은 제 querydsl 관련 gradle 설정입니다. (자바 17 버전입니다. javax를 사용하는 구 버전은 다르게 설정해야 합니다.)

//querydsl 관련 설정
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"

 

Native SQL

Native SQl은 주로 @Query 어노테이션을 이용하거나 EntityManager에 createNativeQuery 메서드를 이용해서 구현합니다. 디비에 의존적인 SQL을 사용하기 때문에 JPQL보다 효율적인 쿼리를 짜야할 때 사용가능합니다. 성능 면에서 뛰어나다는 장점이 있기 때문에 전반적인 쿼리는 Querydsl을 이용하고 성능이 나와야 하는 특정 부분에서만 Native SQL을 사용하는 경우가 있습니다. 단점은 컴파일 시점에 오류를 검증할 수 없다는 것과 유지보수가 힘들 다는 점이 있습니다.

Criteria

Criteria는 EntityManager에서 CriteriaBuilder를 가져와서 구현합니다. Criteria는 JPA를 지원하고 컴파일 시점에 오류를 검증할 수 있다는 장점이 있지만, 조건이 많아지면 코드가 복잡해지고 코드 가독성이 떨어진다는 단점이 있습니다.

 

동적쿼리 3가지 방법 정리

항목 Query DSL Native SQL Criteria
가독성 직관적이고 간결한 구문 SQL 문법 그대로 작성 메서드 체인이 길고 복잡
타입안정성 Q클래스를 이용해 컴파일 타입 오류 방지 SQL이기 때문에 타입 안정성 X 필드명을 문자열로 작성
-> 오타시 런타임에러
조건모듈화 BooleanExpression으로 모듈화 모듈화를 위한 별도 로직 필요 조건 분리가 어렵고 반복 발생
코드재사용성 조건 조합 및 재사용 쉬움 재사용을 위한 추가 처리 필요 특정 조건 재사용이 어려움 / 조건별 추가 작업 필요
성능최적화 fetch join 등 JPA 최적화 기능 구현 DB에 최적화 된 쿼리 작성 가능 복잡한 쿼리는 최적화 어려움
학습곡선 추가 라이브러리 학습은 필요하지만 그 후에는 간단 SQL 자체는 쉬우나 JPA와 함께 사용시 매핑 로직 학습해야함 표준 JPA 이므로 학습 쉬움

 

저는 개인적으로 성능이 크게 중요한 부분이 아닌 곳에서는 가독성과 재사용성을 중요하게 생각하기 때문에 Query DSL을 선택했습니다.

 

💡Query DSL 프로젝트 적용

 

 

 

저는 Spring Data Jpa를 활용 중이었기 때문에

다음과 같은 파일 구조로 query dsl을 사용했습니다.

 

 

 

 

저는 Boolean Expression을 사용해서 모든 조건들을 분리했고 메서드명으로 어떤 조건인지 알 수 있게 하였습니다. 또한 조건들을 분리함으로써 다른 쿼리에서도 이 조건들을 사용할 수 있게 했습니다. 다음은 제 Querydsl 코드의 일부분입니다. 유저의 정보를 where문에서 Boolean Expression으로 만들어진 조건 메서드에 넣어주어 각 조건을 확인할 수 있도록 했습니다. 

// 내가 받을 수 있는 장학금을 가져오는 querydsl 로직
@Override
    public List<Scholarship> findMyScholarship(Member member) {

        return jpaQueryFactory
                .selectFrom(scholarship)
                .where(
                        universityEq(member.getUniversity()),
                        genderEq(member.getGender()),
                        gradeEq(member.getGrade()),
                        incomeEq(member.getIncomeQuantile()),
                        departmentEq(member.getDepartment()),
                        ageEq(member.getAge()),
                        addressEq(member.getProvince(), member.getCity())
                )
                .fetch();
    }
    
    private BooleanExpression gradeEq(Double gradeCond) {

        if(gradeCond == null){
            return scholarship.grade.isNull();
        }

        // 장학금 최소 학점이 없는 경우 + 최소학점보다 내 학점이 높은 경우
        return scholarship.grade.isNull().or(scholarship.grade.loe(gradeCond));
    }

    private BooleanExpression incomeEq(Integer incomeCond) {

        if(incomeCond == null){
            return scholarship.incomeQuantile.isNull();
        }

        return scholarship.incomeQuantile.isNull().or(scholarship.incomeQuantile.goe(incomeCond));
    }

'Spring' 카테고리의 다른 글

스프링 부트에서 예외와 처리 방법  (2) 2024.12.30
Spring Security 필터에서 발생한 인증/인가 예외 처리하는 방법  (2) 2024.12.30
커넥션 풀 vs 복잡한 로직 시간 비용  (2) 2024.12.22
트랜잭션 by JPA  (2) 2024.12.17
연관관계 매핑이 꼭 필요한가  (2) 2024.12.16
'Spring' 카테고리의 다른 글
  • Spring Security 필터에서 발생한 인증/인가 예외 처리하는 방법
  • 커넥션 풀 vs 복잡한 로직 시간 비용
  • 트랜잭션 by JPA
  • 연관관계 매핑이 꼭 필요한가
onetaek
onetaek
finding-scholarship.vercel.app
  • onetaek
    원택투택
    onetaek
  • 전체
    오늘
    어제
    • 전체 (17)
      • Spring (13)
      • Docker (1)
      • Redis (3)
      • Study (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 숨은 장학금 찾기 사이트
  • 공지사항

  • 인기 글

  • 태그

    Spring
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
onetaek
Spring Boot에서 Querydsl 사용
상단으로

티스토리툴바