백엔드/Spring

[Spring] Querydsl

kwang2134 2024. 9. 25. 14:18
728x90
반응형
728x90

Querydsl

  • Java 기반의 타입 안전한 쿼리 DSL(Domain Specific Language)로 데이터베이스에 대한 쿼리를 객체 지향적으로 작성할 수 있게 해주는 도구
  • 다양한 데이터 소스와 통합되어 사용되며 개발자들이 SQL 쿼리를 문자열로 작성하는 대신 타입 안전한 방법으로 쿼리를 생성하고 실행할 수 있도록 도와줌

주요 특징

  • 타입 안전성: 컴파일 타임에 쿼리를 검증하여 잘못된 필드 이름이나 타입에 대한 에러를 예방하고 런타임 에러 발생을 줄임
  • 가독성: SQL 쿼리를 자바 코드 형식으로 작성할 수 있어 가독성이 높음
  • 유연성: 다양한 데이터베이스와 ORM 프레임워크와 호환되며 SQL 뿐만 아니라 JPA, MongoDB, Lucene 등 다양한 쿼리 타입을 지원

설정 방법

  • 개별적인 설정이 필요
//Querydsl 5.0 버전 기준
//build.gradle 의존성 추가

dependencies {    
	//Querydsl JPA 모듈 추가
	implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
	//Querydsl의 애너테이션 프로세서(APT)를 추가 -> Q타입 엔티티 생성, 외부 설정에서 지정한 Querydsl 버전을 참조하여 버전 관리
	annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
	//Jakarta EE에서 제공하는 어노테이션 API를 포함 -> 어노테이션 기반 기능 구현 시 사용
	annotationProcessor "jakarta.annotation:jakarta.annotation-api"
	//JPA 관련 애너테이션을 처리
	annotationProcessor "jakarta.persistence:jakarta.persistence-api"
}

 


사용 방법

  • JPA 엔티티 클래스를 기반으로 Q타입 클래스를 생성
  • EntityManager를 통해 JPAQueryFactory 생성
  • 생성된 JPAQueryManager와 Q타입 엔티티를 통해 쿼리 생성

Q-Type class

  • Querydsl에서 생성하는 메타 모델 클래스
  • JPA 엔티티의 필드에 대한 정보를 담고 있음
  • JPA 엔티티 클래스를 기반으로 자동으로 생성 -> 1대 1 대응 (예: User -> QUser)

Q-Type class 생성 과정

  1. 빌드 도구가 컴파일 수행 시 Querydsl 어노테이션 프로세서 동작
  2. JPA @Entity 어노테이션이 붙은 클래스 식별(@QueryEntity 클래스도 처리 대상)
  3. 식별된 엔티티 클래스에 해당하는 Q-Type 클래스 생성 -> 생성된 클래스는 보통 'target/generated-sources/java' 또는 'build/generated/sources/annotationProcessor/java/main'  디렉터리에 저장
  4. 원본 엔티티의 각 필드에 대해 Q-Type 클래스에 해당하는 필드 생성 -> 쿼리 작성 시 사용 
  5. 생성된 클래스를 사용하여 쿼리 작

Q-Type 사용법

  • Q-Type 객체 내부 원본 객체 이름의 맨 앞 글자를 소문자로 바꾼 정적 인스턴스 생성
  • new 명령어를 통해 별칭을 주어 생성 가능
  • 같은 객체를 조인할 경우 구분을 위해 별칭으로 생성하여 사용
//기본 인스턴스를 사용하는 방법
QUser qUser = QUser.user;

//별칭 생성
QUser qUser = new QUser("qu");

 

JPAQueryFactory

  • 쿼리를 생성하고 실행하는 데 사용하는 클래스
  • SQL과 유사한 이름의 메서드를 제공하여 메서드 체인을 통해 사용
//예시
JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager);

User findUser = queryFactory
            .select(QUser.user)
            .From(QUser.user)
            .where(QUser.user.name.eq("park")
                    .and(QUser.user.age.eq(10)))
            .fetchOne();

 

주요 메서드

  • select(): JPQL의 select절
  • selectFrom(): JPQL의 select와 from에 동일한 객체를 사용 
  • from(): JPQL의 from절
  • where(): JPQL의 where절
  • fetch(): 쿼리를 실행하고 리스트 형태로 반환 -> EntityManager의 getResultList()
  • fetchOne(): 쿼리를 실행하고 단일 결과를 반환 -> EntityManager의 getSingleResult()
  • fetchFirst(): 쿼리를 실행하고 첫 번째 결과를 반환
  • orderBy(): JPQL의 order by 절
  • offset(): 결과의 시작 위치 지정 -> 페이징
  • limit(): 반환할 결과의 최대 개수 지정
  • join(): JPQL의 조인 절
  • fetchJoin(): JPQL fetch join 사용 가능 
  • groupBy(): JPQL의 group by 절
//예시 - join
List<User> usersInGroup = queryFactory
        .select(QUser.user)
        .from(QUser.user)
        .join(QUser.user.group)
        .where(QGroup.group.name.eq("admin"))
        .fetch();
        
//예시 - AND, OR
List<User> users = queryFactory
        .select(QUser.user)
        .from(QUser.user)
        .where(QUser.user.age.goe(18)
                .and(QGroup.group.name.eq("member"))
                .or(QUser.user.name.eq("admin")))
        .fetch();
        
//예시 - paging, sorting
List<User> users = queryFactory
        .select(QUser.user)
        .from(QUser.user)
        .join(QUser.user.group)
        .where(QGroup.group.name.eq("member"))
        .orderBy(QUser.user.age.asc()) 
        .limit(10) 
        .fetch();
        
//예시 - 집합
List<Tuple> averageAges = queryFactory
        .select(QGroup.group.name, QUser.user.age.avg())
        .from(QUser.user)
        .join(QUser.user.group)
        .groupBy(QGroup.group.name) 
        .fetch();
        
//예시 - fetch join
List<User> usersWithGroup = queryFactory
        .selectFrom(QUser.user)
        .join(QUser.user.group).fetchJoin() 
        .where(QGroup.group.name.eq("admin"))
        .fetch();

 

검색 조건 지정

user.name.eq("park")		: = equal
user.name.ne("park")		: != not equal
user.name.eq("park").not()	: ne와 동일

user.name.isNotNull		: != null

user.age.in(10, 20)		: age in(10, 20)
user.age.notIn(10, 20)		: age not in(10, 20)
user.age.between(10, 20)	: age between(10, 20)

user.age.goe(30)		: age >= 30 Greater than or equal to
user.age.gt(30)			: age > 30  Greater than
user.age.loe(30)		: age <= 30 Less than or equal to
user.age.lt(30)			: age < 30  Less than

user.name.like("kim%")		: like 조건 검색
user.name.contains("kim")	: %kim% 조건 검색
user.name.startsWith("kim")	: kim% 조건 검색

 

Case 문

  • 복잡한 조건을 가진 case문 사용 가능
//단순 조건 case 
List<String> result = queryFactory
        .select(QUser.user.age.intValue()
                .when(10).then("열살")
                .when(20).then("스무살")
                .otherwise("기타"))
        .from(QUser.user)
        .fetch();
        
//CaseBuilder 사용 복잡한 조건
List<String> result = queryFactory
        .select(new CaseBuilder()
                .when(QUser.user.age.intValue().between(10, 19)).then("10대")
                .when(QUser.user.age.intValue().between(20, 29)).then("20대")
                .otherwise("기타"))
        .from(QUser.user)
        .fetch();

 

상수 & 문자 더하기

  • select 절에서 Expressions.constant()를 사용하여 상수를 더 할 수 있음
  • concat을 사용해 문자 더하기가 가능 -> 숫자일 경우 stringValue()를 사용하여 문자로 변환 
//상수 더하기 Expressions
Tuple result = queryFactory
        .select(QUser.user.name, Expressions.constant("A"))
        .from(QUser.user)
        .fetchFirst()
        
//문자 더하기 concat()
String result = queryFactory
        .select(QUser.user.name.concat("_").concat(QUser.user.age.stringValue()))
        .from(QUser.user)
        .where(QUser.user.name.eq("park"))
        .fetchOne();

 

Tuple

  • 여러 개의 값(컬럼)을 그룹화하여 반환할 수 있는 데이터 구조
  • 쿼리 결과에서 여러 필드를 동시에 가져와야 할 때 사용
  • Tuple 객체에서 값에 접근 시 인덱스 또는 이름 사용 가능
  • 집계 쿼리나 다양한 쿼리와 조합 가능
//인덱스 접근
String userName = tuple.get(0, String.class); // 첫 번째 값 (사용자 이름)
String groupName = tuple.get(1, String.class); // 두 번째 값 (그룹 이름)

//이름 접근
String userName = tuple.get(QUser.user.name);
String groupName = tuple.get(QGroup.group.name);

//집계 쿼리 사용 시
String maxAge = tuple.get(QUser.user.age.max());

실전! Querydsl 강의 | 김영한 - 인프런 (inflearn.com) 강의 내용 참고

 

728x90

'백엔드 > Spring' 카테고리의 다른 글

[Spring] 스프링 부트  (2) 2024.09.27
[Spring] Querydsl DTO & 동적 쿼리  (1) 2024.09.26
[Spring] Spring Data Jpa 확장 기능  (0) 2024.09.24
[Spring] 쿼리 메서드  (0) 2024.09.23
[Spring] Spring Data JPA  (2) 2024.09.22