[JPA] 객체 지향 쿼리 언어 - QueryDSL(1)

Lee Seung Jae·2021년 7월 31일
0

QueryDSL

Criteria의 단점 너무 복잡하고 어렵다는 것 그래서 JPQL이 어떻게 생성되는지 파악이 어렵다.
그래서 나온게 이 QueryDSL이다.. 코드로 작성하는데 간결하고 알아보기 쉽다.

QueryDSL은 오픈소스 프로젝트이다. 단순 CRUD보다는 이름에 걸맞게 데이터를 조회 그러니까 통계형 쿼리를 짤때 적합하지 않을까 생각한다.

QueryDSL Setting

build.gradle

buildscript {
    ext {
        ...
        querydslVersion = '1.0.10'
    }
    
    dependencies {
        ....
        classpath "gradle.plugin.com.ewerk.gradle.plugins:querydsl-plugin:$querydslVersion"
    }
}

subprojects {
    apply plugin: 'com.ewerk.gradle.plugins.querydsl'
    
    ext {
        querydslDir = "$buildDir/generated/querydsl"
    }
    
    dependencies {
        ...
        implementation 'com.querydsl:querydsl-jpa'
    }
    
    querydsl {
        jpa = true
        querydslSourcesDir = querydslDir
    }

    sourceSets {
        main.java.srcDir querydslDir
    }

    configurations {
        querydsl.extendsFrom compileClasspath
    }

    compileQuerydsl {
        options.annotationProcessorPath = configurations.querydsl
    }
}

이렇게 설정을 해주었는데 중요한 부분은 subproject.ext.querydslDir 부분이다.
buildDir이뜻하는것은우리의스터디는일단모듈을나누어서한사람당모듈을사용하고있다.그래서의존성을구분해놓았는데여기서의buildDir이 뜻하는 것은 우리의 스터디는 일단 모듈을 나누어서 한사람당 모듈을 사용하고 있다. 그래서 의존성을 구분해놓았는데 여기서의 `buildDir은 모듈의 빌드된 폴더 build/를 의미하며 build/generated/querydsl` 폴더에 Entity클래스 앞에 Q가 붙은 클래스가 빌드되어 있다.
이것으로 QueryDSL을 세팅해주는 것이다.

QuerydslConfig.java

@Configuration
public class QuerydslConfig {
    @PersistenceContext
    private EntityManager entityManager;

    @Bean
    public JPAQueryFactory jpaQueryFactory() {
        return new JPAQueryFactory(entityManager);
    }
}

전에 말했듯 JavaEE환경에서는 @PersistenceContext를 활성화하면 알아서 주입받는다.
JPAQueryFactory 가 QueryDSL을 사용하기 위해서 구현해야 하는 것이다.

이렇게 설정해주면 QueryDSL을 사용할 수가 있다.

이제 테스트 코드를 작성해 보자.

@DataJpaTest
public class QuerydslTest {
    @PersistenceUnit
    EntityManagerFactory emf;
    EntityManager em;
    EntityTransaction tx;
    JPAQueryFactory jpaQueryFactory;

    private Member member;

    @BeforeEach
    void setUp() {
        em = emf.createEntityManager();
        tx = em.getTransaction();
        jpaQueryFactory = new JPAQueryFactory(em);
        tx.begin();
        Address address = Address.builder()
                .city("city")
                .street("street")
                .zipcode("zipcode")
                .build();

        Address comAddress = Address.builder()
                .city("cocity")
                .street("costreet")
                .zipcode("cozipcode")
                .build();

        Period period = Period.of("20210714", "20210714");

        member = Member.builder()
                .name("kim")
                .age(26)
                .period(period)
                .homeAddress(address)
                .companyAddress(comAddress)
                .build();

        em.persist(member);
        em.flush();
    }

    @Test
    @DisplayName("QueryDSL 시작")
    void querydslTest() {
        QMember qMember = new QMember("m"); //생성된 별칭
        List<Member> members = jpaQueryFactory.selectFrom(qMember)
                .where(qMember.name.eq("kim")).orderBy(qMember.id.desc()).fetch();

        assertThat(members.get(0).getName()).isEqualTo("kim");
        assertThat(members.get(0).getAge()).isEqualTo(26);
    }
}

책에서는 orderBy(qMember.id.desc()).list(qMember); 로 사용했는데
버전이 바뀌면서 list 메소드는 없어졌다고 한다.
그래서 공식문서를 찾아보니까

QueryDSL로 select 조회 쿼리를 만들었을때 로그이다.

이상한점 이라고 느낄수 있다면 어? 하고 코드상에는 jpaQueryFactory.from으로 시작하기 때문에 이것이 어떤 CRUD인지 모를 수 있다.

fetch()를 해주면 반환이 List인데 그래도 타입은 캐스팅을 해주어야 한다.
자동으로 뭔가 Q클래스를 맞춰줄줄 알았다.
🤣

ㅋㅋㅋㅋㅋㅋㅋ 아니었다..... 그냥 기본값이 select가 아니라 list(qMember) 를 해주던 버전에서는 이 list 메소드가 엔티티 타입을 맞춰서 List에 넣어주었는데 지금은 selectFrom(qMember)를 해주면 이것이 List 타입을 맞춰준다.
이렇게 또 하나 깨달음을 얻는다 👍

페이징 정렬

@Test
void 페이징_테스트() {
    QItem item = QItem.item;
    List<Item> result = jpaQueryFactory.selectFrom(item).where(item.price.lt(500))
                                           .orderBy(item.price.desc(), item.name.desc())
                                           .offset(1).limit(3).fetch();

    result.forEach(r -> System.out.println(r.toString()));
}

여기서 where 조건에 lt는 부등호로 < 이고, gt는 >이다. orderBy절에서는 쿼리 타입인(Q)에서 asc(), desc()를 지원해준다. 페이징은 offsetlimit 을 조합해서 사용하면 된다.
이렇게해서 얻은 결과이다.

전체 데이터 수를 알고 싶을때는 listResults()를 사용한다. 이것 역시 바뀌었다.
SearchResults<T> 가 아니라 버전이 바뀌면서 QueryResults<T>로 변경되었다.

listResults() 가 아니라 fetchResults()로 바뀌게 되었다.

@Test
@DisplayName("조회 결과 테스트")
void listResultsTest() {
    QueryResults<Item> result = jpaQueryFactory.selectFrom(item).where(item.price.lt(500))
                .orderBy(item.price.desc(), item.name.desc())
                .offset(1).limit(3).fetchResults();

    long total = result.getTotal(); //500보다 작은수 총 count
    long limit = result.getLimit(); //limit 3
    long offset = result.getOffset(); // offset 1
    List<Item> results = result.getResults();

    assertThat(total).isEqualTo(4L);
    assertThat(limit).isEqualTo(3L);
    assertThat(offset).isEqualTo(1L);

    results.forEach(r -> System.out.println(r.toString()));
}

getTotal은 오류의 여지가 있어보인다. 왜냐면 저 QueryDSL 전체를 시켰을때의 count가 아니라 count가 먼저 실행되기 때문에 where() 조건까지 수행한 count가 나온다.

결과의 조회는 QueryResultsgetResults()를 사용하여 페이징 정렬할때처럼의 결과를 얻을수가 있다.

그룹

그룹은 groupBy를 사용하고 그 다음에 조건을 해주려면 having을 사용하면 된다.

List<Item> results = jpaQueryFactory.selectFrom(item)
                                    .groupBy(item.price)
                                    .having(item.price.lt(500))
                                    .fetch();

profile
💻 많이 짜보고 많이 경험해보자 https://lsj8367.tistory.com/ 블로그 주소 옮김

0개의 댓글