기본 문법 - Part1


들어가기 앞서…


Querydsl란?

• Querydsl은 JPQL 빌더

 오류 시점파라미터
JPQL실행 시직접 바인딩
Querydsl컴파일 시자동 바인딩


동시성 문제

JPAQueryFactory를 필드로 제공하면 동시성 문제는 어떻게 될까?

동시성 문제는 JPAQueryFactory를 생성할 때 제공하는 EntityManager에 달려있다.

스프링 프레임워크는 여러 쓰레드에서 동시에 같은 EntityManager에 접근해도,

트랜잭션 마다 별도의 영속성 컨텍스트를 제공하기 때문에, 동시성 문제는 걱정하지

않아도 된다.


기본 Q-Type 활용


Q클래스 인스턴스를 사용하는 2가지 방법

같은 테이블을 조인해야 하는 경우가 아니면 기본 인스턴스를 사용한다.

QMember qmember = new QMember("m"); // 별칭 직접 지정
QMember qmember = QMember.member;   // 기본 인스턴스 사용 


기본 인스턴스를 static import와 함께 사용

import static study.querydsl.entity.QMember.*;

@SpringBootTest
@Transactional
@Rollback(value = false)
public class QuerydslBasicTest {

    @PersistenceContext EntityManager em;
    JPAQueryFactory queryFactory = new JPAQueryFactory(em);

    @Test
    public void qTypeTest() {

        Member findMember = queryFactory
                        .selectFrom(member)
                        .where(member.username.eq("member1"))
                        .fetchOne();

    }
}


검색 조건


기본

검색조건은 and(), or() 메서드 체인으로 연결할 수 있다.

@Test
public void search() {

    Member findMember = queryFactory
            .selectFrom(member)
            .where(member.username.eq("member1").and(member.age.eq(10)))
            .fetchOne();

    assert findMember != null;
    assertThat(findMember.getUsername()).isEqualTo("member1");
}


AND 조건을 파라미터로 처리

where() 의 경우에 파라미터로 검색조건을 추가하면 AND조건이 추가된다.

@Test
public void searchAndParam() {

    Member findMember = queryFactory.selectFrom(member)
            .where(
                    member.username.eq("member1"),
                    (member.age.eq(10))
            ).fetchOne();

    assert findMember != null;
    assertThat(findMember.getUsername()).isEqualTo("member1");
}


결과 조회


기본

메소드설명비고
fetch()리스트 조회데이터 없으면 빈 리스트 반환
fetchOne()단 건 조회결과 없다 : null / 둘 이상 : NonuniqueResultException
fetchFirst()limit(1).fetchOne()-
fetchResults()페이징 정보 포함, total count 쿼리 추가 실행deprecated(from 5.0.0)
fetchCount()count 쿼리로 변경해서 count 수 조회deprecated(from 5.0.0)


deprecated

우선 더 이상 지원하지 않는 이유를 알아봤다.

QueryResults 형식으로 프로젝션을 가져옵니다. QueryResults.getOffset() 또는 QueryResults.getLimit()에 의존하지 않는 경우 성능이 더 좋으므로 fetch()를 사용하십시오. 또한 모든 방언에 대해 개수 쿼리를 제대로 생성할 수 없습니다. 예를 들어 JPA에서 count 쿼리는 여러 group by 표현식 또는 having 절이 있는 쿼리에 대해 생성할 수 없습니다.


결국 Data JPA에서 성능 최적하를 위해 본 쿼리와 카운트 쿼리를 나눠서

실행한다고 공부했었는데 여기서도 그런 방식으로 진행을 해야할 것 같다.


@Test
public void paging() throws Exception {

    PageRequest pageRequest
        = PageRequest.of(0, 2, Sort.by(Sort.Direction.DESC, "username"));
        
    // 페이징 쿼리
    List<Member> result = queryFactory
            .selectFrom(member)
            .offset(0)
            .limit(9)
            .fetch();
    
    // 카운트 쿼리
    int size = queryFactory
            .selectFrom(member)
            .fetch()
            .size();
    
    PageImpl<Member> members = new PageImpl<>(result, pageRequest, size);

}


추가적으로 Page 인터페이스의 구현체인 PageImpl<T> 클래스에서 총 페이지수,

다음 페이지가 있는지 여부, 마지막 페이지인지 여부 등등 다양한 메소드를 제공한다.

package : org.springframework.data.domain

public class PageImpl<T> extends Chunk<T> implements Page<T> {
        //... 다양한 메서드 제공
}


정렬


예시 코드


@Test
@DisplayName("회원 정렬 순서")
public void sorting() throws Exception {
    //given
    em.persist(Member.of(null, 100));
    em.persist(Member.of("member5", 100));
    em.persist(Member.of("member6", 100));

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

    //then
    Member member5 = findMemberList.get(0);
    Member member6 = findMemberList.get(1);
    Member memberNull = findMemberList.get(2);
    assertThat(member5.getUsername()).isEqualTo("member5");
    assertThat(member6.getUsername()).isEqualTo("member6");
    assertThat(memberNull.getUsername()).isNull();
}


페이징


예시 코드

위에 정리해둔 결과조회 - deprecated를 보면 된다.


집합


예시 코드

@Test
@DisplayName("집합")
public void aggregation() throws Exception {

    List<Tuple> result = queryFactory
            .select(member.count(),
                    member.age.sum(),
                    member.age.avg(),
                    member.age.max(),
                    member.age.min())
            .from(member)
            .fetch();

    Tuple tuple = result.get(0);

    assertThat(tuple.get(member.count())).isEqualTo(4);
    assertThat(tuple.get(member.age.sum())).isEqualTo(100);
    assertThat(tuple.get(member.age.avg())).isEqualTo(25);
    assertThat(tuple.get(member.age.max())).isEqualTo(40);
    assertThat(tuple.get(member.age.min())).isEqualTo(10);

}
@Test
@DisplayName("팀의 이름과 각 팀의 평균 연령을 구하기")
public void group() throws Exception {
    //given
    List<Tuple> tupleList = queryFactory
            .select(team.name, member.age.avg())
            .from(member)
            .join(member.team, team)
            .groupBy(team.name)
            .fetch();

    Tuple teamA = tupleList.get(0);
    Tuple teamB = tupleList.get(1);
    assertThat(teamA.get(team.name)).isEqualTo("teamA");
    assertThat(teamA.get(member.age.avg())).isEqualTo(15);
    assertThat(teamB.get(team.name)).isEqualTo("teamB");
    assertThat(teamB.get(member.age.avg())).isEqualTo(35);
}