QueryDSL

 

특징

자바 코드로 query를 보낼 수 있다. -> 컴파일 과정에서 문법 오류를 발견할 수 있다.

 

세팅

build.gradle

plugins와 queryDSL이 자동으로 생성하는 디렉토리 경로

plugins {
    id 'org.springframework.boot' version '2.2.2.RELEASE'
    id 'io.spring.dependency-management' version '1.0.8.RELEASE' //querydsl 추가
    id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
    id 'java'
}

//querydsl 추가 시작
def querydslDir = "$buildDir/generated/querydsl"
    querydsl {
    jpa = true
          querydslSourcesDir = querydslDir
      }
      sourceSets {
          main.java.srcDir querydslDir
      }
      configurations {
          querydsl.extendsFrom compileClasspath
      }
      compileQuerydsl {
          options.annotationProcessorPath = configurations.querydsl
}
//querydsl 추가 끝

plugins에 querydsl과 버전이 추가되어있는 것과

querydsl에서 제공하는 기능을 사용하기 위해 필요한 설정에 대해 이런게 있구나 정도로 넘어간다.

 

generated 생성하기

Gradle in side tab -> Tasks -> other -> compileQuerydsl

 

기본 활용

// EntityManager em;
JPAQueryFactory queryFactory = new JPAQueryFactory(em);

QMember m = new QMember("m");

Member findMember = queryFactory
        .select(m)
        .from(m)
        .where(m.username.eq("member1"))
        .fetchOne();

JPAQueryFactory은 멀티 쓰레딩에 안전하게 설계되었으므로 공유되어 사용해도 좋다.

동시성 문제는 EntityManager에 달려있고 transaction마다 별도의 영속성 컨텍스트를 제공하기 때문에 안전하다.

 

참고
where의 여러 인수로 predicate를 넘기면 and로 연결한다.

 

Q-Type 활용

QMember qMember = new QMember("m"); // alias 직접 지정
QMember qMember = QMember.member; // 내부에서 생성한 기본 인스턴스 사용

위의 QMember.member는 QMember의 public static 멤버 변수이다.
따라서 변수로 초기화하여 사용할 필요없이 QMember.member로 사용가능하다.
역시 static 변수 member의 타입은 QMember이다.
참고로 내부에서 생성했다함은 QMember에 new QMember("member1");처럼 생성된 것을 말한다.
즉, query의 alias로 member1이 사용된다는 의미이다.

일반적으로 QMember.member 처럼 static 변수로 사용하는 것을 권장한다.

그리고 사실 alias를 위한 variable을 지정했을 때의 장점을 모르겠다.

 

검색 조건

member.username.eq("정상수") // username = '정상수'
member.username.ne("정상수") // username != '정상수'
member.username.eq("정상수").not() // username != "정상수"

member.username.isNotNull()

member.age.in(10, 20) // age in (10, 20)
member.age.notIn(10, 20) // age not in (10, 20)
member.age.between(10, 30) // between 10, 30

member.age.goe(30) // age >= 30
member.age.gt(30) // age > 30
member.age.loe(30) // age <= 30
member.age.lt(30) // age < 30

member.username.like("member%") // like 검색
member.username.contains("member") // like '%member%' 검색
member.username.startsWith("member") // like 'member%' 검색

 

참고

like 연산자는 와일드카드를 이용하여 검색할 수 있다.
와일드 카드에는 %, _가 있다.

 

결과 조회

  List<Member> fetch = queryFactory
          .selectFrom(member)
          .fetch();

// 단건
// 결과가 없으면 null
// 결과가 둘 이상이면 NonUniqueResultException
Member findMember1 = queryFactory
          .selectFrom(member)
          .fetchOne();

// 처음 한 건 조회
// limit(1).fetchOne()
Member findMember2 = queryFactory
          .selectFrom(member)
          .fetchFirst();

// 페이징에서 사용
// 성능이 중요하면 쿼리를 나누는 것이 좋을 수도
QueryResults<Member> results = queryFactory
          .selectFrom(member)
          .fetchResults();

// count 쿼리로 변경
long count = queryFactory
          .selectFrom(member)
          .fetchCount();

fetchResults()에 대해서 자세히 알아보자.

QueryResults<Member> results = queryFactory
          .selectFrom(member)
          .fetchResults();

results.getTotal();
List<Member> content = results.getResults();

위 코드를 실행하면 query가 두 번 실행된다는 특징이 있다.
total count를 얻는 query와 질의 query가 따로 나간다.

count query

count query

select query

select query in fetchResults

 

참고
위 /* 주석으로 덮인 select는 JPQL, 아닌 것은 sql statement

# application.property

# JPQL을 보는 방법
spring.jpa.properties.hibernate.use_sql_comments: true

 

정렬

List<Member> result = queryFactory
              .selectFrom(member)
              .where(member.age.eq(100))
              .orderBy(member.age.desc(), member.username.asc().nullsLast())
              .fetch();

주의해서 봐야할 것은

where에 필요한 BooleanExpression(Predicate)는 .eq method
orderBy에 필요한 OrderSpecifier(Comparable) 로 표현했다.

'스프링' 카테고리의 다른 글

QueryDSL 볶아먹기  (0) 2023.10.22
QueryDSL 다져먹기  (0) 2023.10.21
테스트 코드 정의와 이점, 왜 해야 할까? 그리고 무엇을 해야 할까?  (0) 2023.07.08
JPA란?  (0) 2023.07.06
서블릿이 뭔데?  (0) 2023.06.07