JPA/QueryDSL

[QueryDSL] Expression( 표현 )

IT록흐 2023. 8. 4. 01:12
반응형

 

 

[QueryDSL] QueryDSL 동작원리(3) - fetch

QueryDSL은 JPQL 생성 및 실행 권한을 자신에게 위임하여, 개발자가 JPQL을 직접 작성했을 때 발생하는 타입 안정성 체크의 어려움이나 동적 쿼리생성 문제를 해결할 수 있다. 개발자는 그저 JPQL 생성

lordofkangs.tistory.com

 

QueryDSL은 JPQL 생성 및 실행 권한을 자신에게 위임하여, 개발자가 JPQL을 직접 작성했을 때 발생하는 타입 안정성 문제나 동적쿼리생성 문제를 해결할 수 있다. 개발자는 그저 JPQL 생성을 위한 메타데이터만 설정하면 된다. QueryDSL은 유연한 데이터 설정을 지원하기 위해 빌더패턴 구조로 이루어져 있다. 여기까지가 지난 포스팅에서 다루었던 내용이다. 

 

개발자(클라이언트)가 QueryDSL에게 원하는 스펙에 맞는 JPQL 생성및실행을 요구하려면 SELECT절, WHERE절, FROM절 등에 Projection이나 검색조건 같은 SQL 요소를 적절히 '표현(Expression)'해야 한다.  QueryDSL은 개발자가 가독성있게 요구사항을 표현할 수 있도록 인터페이스를 하나를 제공하는데, 그것이 Expression이다. 

 

이번 포스팅은 Expression에 대해서 다루어 보겠다.

 

MemberRepository

import static study.querydsl.entity.QMember.member; // Q타입 클래스의 전역상수객체
import static study.querydsl.entity.QTeam.team; // Q타입 클래스의 전역상수객체

public class MemberRepositoryImpl implements MemberRepositoryCustom{

    private final JPAQueryFactory queryFactory;

    public MemberRepositoryImpl(EntityManager entityManager){
        this.queryFactory = new JPAQueryFactory(entityManager); // JPAQueryFactory 
    }

	
    public List<Member> searchMember(MemberSearchCondition condition){
        return queryFactory
                .select(member) // Expression : member
                .from(member) // Expression : member
                .leftJoin(member.team, team) // Expression : member.team
                .where(
                        usernameEq(condition.getUsername()), 
                        teamNameEq(condition.getTeamName()),
                        ageGoe(condition.getAgeGoe()),
                        ageLoe(condition.getAgeLoe())
                )
                .fetch();
    }

    private BooleanExpression ageBetween(int ageLoe, int ageGoe){
        return ageLoe(ageLoe).and(ageGoe(ageGoe));
    }

    private BooleanExpression ageLoe(Integer ageLoe) {
        return ageLoe != null ? member.age.loe(ageLoe) : null; // Expression : member.age.loe
    }

    private BooleanExpression ageGoe(Integer ageGoe) {
        return ageGoe != null ? member.age.goe(ageGoe) : null; // Expression : member.age.goe
    }

    private BooleanExpression teamNameEq(String teamName) {
        return hasText(teamName) ? team.name.eq(teamName) : null; // Expression : team.name.eq
    }

    private BooleanExpression usernameEq(String username) { 
    	return hasText(username) ? member.username.eq(username) : null; // Expression : member.username.eq
    }
}

 

위 코드는 Repository(클라이언트)가 QueryDSL에게 원하는 JPQL 스펙을 요구하는 예시코드이다. 

 

member, member.team, member.age, member.age.goe 등은 select 메소드, from 메소등, where 메소드의 매개변수로 전달되는 Expression, 즉 '표현'이다. QueryDSL에서 메소드에 세팅되는 데이터는 모두 Expression 인터페이스의 구현체이다.

 

select(member)는 Projection을 Member 속성 전체로 한다는 표현이고

from(member)는 Member 테이블에 조회를 한다는 표현이고

leftjoin(member.team)은 Member와 연관된 Team과 조인한다는 표현이다. 

 

클라이언트의 요구사항이 담긴 데이터를 표현에 담아 JPAQueryFactory의 메소드에 넘기는 것이다. 그러므로 QueryDSL은 크게 두 가지 영역으로 나뉜다.

 

 

QueryDSL은  메타데이터 표현 영역과 JPQL 생성 및 실행영역으로 나뉜다. 두 영역의 만남은 JPAQueryFactory의 메소드가 매개변수로 Expression을 받으면서 이루어진다. JPQL을 생성 및 실행하려면 JPQL을 생성을 위한 메타데이터가 필요하다. 그럼 JPAQueryFactory에 메타데이터를 넘겨주어야 하는데, 메타데이터를 표현하는 그릇역할을 하는 것이 Expression이다. 

 

Expression

 

Expression은 '표현'이다. 

QueryDSL에서는 어떤 데이터를 '표현'해야 할까?

 

SELECT절, WHERE절, FROM절, JOIN절.. 에 들어갈 데이터를 표현해야 한다. SQL은 테이블-컬럼 관계이고 JPQL은 엔티티-필드 관계이다. QueryDSL도 관계가 있어야 직관적인 '표현'이 가능해진다. QueryDSL은 Q타입클래스-속성 관계이다.

 

QMember 클래스

@Generated("com.querydsl.codegen.DefaultEntitySerializer")
public class QMember extends EntityPathBase<Member> {

    public static final QMember member = new QMember("member1"); // 전역상수로 제공하는 Q타입 객체
    public final NumberPath<Integer> age = createNumber("age", Integer.class); // 필드1
    public final NumberPath<Long> id = createNumber("id", Long.class); // 필드2
    public final QTeam team; // 필드3
    public final StringPath username = createString("username"); // 필드4

	//중략...
}

 

QMember 클래스는 Member 엔티티를 토대로 만들어진 Q타입 클래스이다.

NumberPath, StringPath, QTeam ,BooleanPath 등은 QueryDSL의 일종의 자료형이라 할 수 있다.  그리고 QueryDSL의 자료형은  모두 Expression 인터페이스의 구현체이다.

 

 

 

 

자료형 간의 Q타입클래스-속성 관계를 만들어야 한다.

 

각 자료형은 DslExpression 클래스를 공통으로 상속하고 있다. DslExpression 클래스는 PathImpl 객체와 연관되어 있다. PathImpl 객체는 해당 자료형이 Q타입클래스인지 Property인지, Property라면 부모(Q타입클래스)는 누구인지, Q타입 클래스는 어떤 엔티티 혹은 객체를 토대로 만들어졌는지, Property는 Integer인지, String인지 boolean인지와 같은 핵심 메타데이터를 가지고 있다. 즉, Q타입클래스-속성 관계에 관한 메타데이터는 각 자료형이 모두 가지고 있는 PathImpl 객체에서 저장되어 관리된다. 이로써, 테이블-컬럼, 엔티티-필드 같은 Q타입클래스-속성 관계가 만들어져, member, member.team, member.age, member.age.goe 같은 직관적인 표현이 가능해진다. 

 

 

 

 

 

그러나 Q타입클래스-속성 관계보다 세분화된 표현도 존재한다.

 

◆ 상품(QProduct, Q타입클래스) 중에 가격(price, 속성)이 가장 비싼(?) 상품은?

◆ 회원(QMember, Q타입클래스) 중에 12살보다 나이(age,속성)가 많은(?) 회원은?

 

개발자(클라이언트)는 QueryDSL에 위 표현을 요구할 수 있다. 그러므로 Expression은 위와 같이 세분화된 표현도 제공할 수 있어야 한다. 그래서 각 자료형은 타입에 맞는 Expression 추상클래스를 상속한다. Expression 추상클래스는 세분화된 표현을 제공할 수 있는 메소드를 제공한다. 

 

//예시1 
queryFactory
	.select(qProduct.price.max()) // NumberExpression의 max 메소드
	.from(qProduct)
	.fetchOne();
    
//예시2    
queryFactory
     .selectFrom(qMember)
     .where(qMember.age.goe(targetAge)) // NumberExpression의 goe 메소드 
     .fetch();

//예시3
queryFactory
     .selectFrom(qTeam)
     .where(qTeam.name.eq(teamName)) // SimpleExpression eq 메소드
     .fetchOne();

 

예시1을 보자.

'Product 테이블의 price 속성의 최댓값'의 표현 = qProduct.price.max()  

 

qProduct.price.max() 은 Expression 인터페이스의 구현체로 개발자의 요구사항을 표현한 객체이다. 그러므로 예시1은 개발자가 QueryDSL에게 SELECT절은 Product 테이블의 price 속성의 최댓값을 projection 할거라는 요구사항을 전달한 코드라고 할 수 있다.

 

예시2는 나이가 targetAge 이상인 회원을 필터링하는 JPQL 생성및실행을 요구하는 코드이다. qMember.age.goe(targetAge)는 나이가 targetAge 이상이라는 검색조건을 표현한 객체이다. 검색조건의 표현은 where 메소드의 매개변수로 넘기면 된다. 

 

예시3은 이름이 teamname과 동일한 팀을 필터링하는 JPQL 생성및실행을 요구하는 코드이다. qTeam.name.eq(teamName)은  이름이 teamname과 동일한 팀을 검색하는 조건을 표현한 객체이다. 검색조건의 표현은 where 메소드의 매개변수로 넘기면 된다. 

 

 

지금까지의 내용을 정리하면,

 

QueryDSL은 두 가지 영역으로 나뉜다. 

 

1) 메타데이터 표현 영역

2) JPQL 생성및실행 영역

 

개발자(클라이언트)는 QueryDSL이 올바른 JPQL을 생성할 수 있도록 SELECT절, FROM절, WHERE 절에  적절한 '표현'을 해야 한다. QueryDSL은 개발자(클라이언트)가 적절히 표현할 수 있도록 Q타입클래스를 제공한다. Q타입클래스는 여러 자료형을 갖는데, 자료형은 모두 Expression 인터페이스를 구현한 구현체이다. SQL이나 JPA는 테이블-컬럼, 엔티티-필드 관계를 갖는다. QueryDSL도 Q타입클래스-속성 관계를 유지해야 직관적인 표현이 가능해진다. 모든 자료형이 공통으로 갖는 PahtImpl 객체는 관계의 정보가 담긴 메타데이터를 관리한다. 이로써 Q타입클래스-속성 관계가 형성되어 Q타입클래스를 마치 엔티티-필드처럼 사용할 수 있다. 여기서 조금 더 세분화된 표현을 하려면 각 자료형이 상속하고 있는 추상 Expression의 메소드를 사용하면 된다. 이로써 SELECT절, WHERE절, FROM절에 클라이언트가 원하는 적절한 데이터가 표현될 수 있다. 

 

 

 


 

 

 

참고자료

 

 

3장. 일반 사용법

Querydsl에서 Query를 생성하려면 표현식 인자를 이용해서 query 메서드를 호출한다. query 메서드는 모듈에 따라 다르고 이미 튜토리얼에서 설명했으므로, 본 절에서는 표현식에 초점을 맞출 것이다.

querydsl.com

 

QueryDSL 시작하기

QueryDSL

hckcksrl.medium.com

 

실전! Querydsl - 인프런 | 강의

Querydsl의 기초부터 실무 활용까지, 한번에 해결해보세요!, 복잡한 쿼리, 동적 쿼리는 이제 안녕! Querydsl로 자바 백엔드 기술을 단단하게. 🚩 본 강의는 로드맵 과정입니다. 본 강의는 자바 백엔

www.inflearn.com

 

반응형