[QueryDSL] 기본 문법 ①

kiteB·2021년 12월 1일
0

QueryDSL

목록 보기
3/9
post-thumbnail

[ 시작 - JPQL vs Querydsl ]

1. 테스트 기본 코드

@SpringBootTest
@Transactional
public class QuerydslBasicTest {
    
    @PersistenceContext
    EntityManager em;
    
    @BeforeEach
    public void before() {
        Team teamA = new Team("teamA");
        Team teamB = new Team("teamB");
        em.persist(teamA);
        em.persist(teamB);
        
        Member member1 = new Member("member1", 10, teamA);
        Member member2 = new Member("member2", 20, teamA);
        Member member3 = new Member("member3", 30, teamB);
        Member member4 = new Member("member4", 40, teamB);
        
        em.persist(member1);
        em.persist(member2);
        em.persist(member3);
        em.persist(member4);
    }
}

앞으로 이 예제를 이용할 것이다!


2. Querydsl vs JPQL

@Test
public void startJPQL() {

    //member1을 찾아라.
    String qlString =
        "select m from Member m " +
        "where m.username = :username";
        
    Member findMember = em.createQuery(qlString, Member.class)
        .setParameter("username", "member1")
        .getSingleResult();
       
    assertThat(findMember.getUsername()).isEqualTo("member1");
}

@Test
public void startQuerydsl() {

    //member1을 찾아라.
    JPAQueryFactory queryFactory = new JPAQueryFactory(em);
    QMember m = new QMember("m");
    
    Member findMember = queryFactory
        .select(m)
        .from(m)
        .where(m.username.eq("member1")) //파라미터 바인딩 처리
        .fetchOne();
    
    assertThat(findMember.getUsername()).isEqualTo("member1");
}
  • EntityManagerJPAQueryFactory 생성
  • Querydsl은 JPQL 빌더
  • JPQL: 문자 (실행 시점 오류) vs Querydsl: 코드 (컴파일 시점 오류)
  • JPQL: 파라미터 바인딩 직접 vs Querydsl: 파라미터 바인딩 자동 처리

3. JPAQueryFactory를 필드로

@SpringBootTest
@Transactional
public class QuerydslBasicTest {

    @PersistenceContext
    EntityManager em;
    
    JPAQueryFactory queryFactory;
    
    @BeforeEach
    public void before() {
        queryFactory = new JPAQueryFactory(em);
        //…
    }
    
    @Test
    public void startQuerydsl2() {
        //member1을 찾아라.
        QMember m = new QMember("m");
        Member findMember = queryFactory
            .select(m)
            .from(m)
            .where(m.username.eq("member1"))
            .fetchOne();
        assertThat(findMember.getUsername()).isEqualTo("member1");
    }
}

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

스프링은 여러 쓰레드에서 동시에 같은 EntityManager에 접근해도, 트랜잭션마다 별도의 영속성 컨텍스트를 제공하기 때문에, 동시성 문제는 걱정하지 않아도 된다!


[ 기본 Q-Type 활용 ]

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

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

앞에서는 Q클래스 인스턴스를 new를 사용하여 별칭을 직접 지정한 방법을 사용했는데, Querydsl가 기본적으로 제공하는 기본 인스턴스를 사용하는 것이 더 간편하다.

2. 기본 인스턴스를 사용하는 방법

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

@Test
public void startQuerydsl3() {
    //member1을 찾아라.
    Member findMember = queryFactory
        .select(member)
        .from(member)
        .where(member.username.eq("member1"))
        .fetchOne();
    
    assertThat(findMember.getUsername()).isEqualTo("member1");
}
  • static import를 사용하면 간편하게 사용할 수 있다.
  • 같은 테이블을 조인해야 하는 경우는 별칭을 따로 지정해야 하지만, 그외의 경우는 기본 인스턴스를 사용하도록 하자.

[ 검색 조건 쿼리 ]

1. 기본 검색 쿼리

@Test
public void search() {
    Member findMember = queryFactory
        .selectFrom(member)
        .where(member.username.eq("member1")
        .and(member.age.eq(10)))
        .fetchOne();
    
    assertThat(findMember.getUsername()).isEqualTo("member1");
}
  • 검색 조건은 .and(), .or()를 메서드 체인으로 연결할 수 있다.
  • selectFrom: select, from을 합쳤다.

2. JPQL이 제공하는 모든 검색 조건 제공

member.username.eq("member1") // username = 'member1'
member.username.ne("member1") //username != 'member1'
member.username.eq("member1").not() // username != 'member1'

member.username.isNotNull() //이름이 is not null

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%’ 검색
...

3. AND 조건을 파라미터로 처리

@Test
public void searchAndParam() {
    List<Member> result1 = queryFactory
        .selectFrom(member)
        .where(
                member.username.eq("member1"),
                member.age.eq(10))
        .fetch();
    
    assertThat(result1.size()).isEqualTo(1);
}
  • where()에 파라미터로 검색 조건을 추가하면 AND 조건이 추가됨.
  • 이 경우 null 값은 무시된다. → 메서드 추출을 활용해서 동적 쿼리를 깔끔해서 만들 수 있다!

[ 결과 조회 ]

  • fetch(): 리스트 조회, 데이터 없으면 빈 리스트 반환
  • fetchOne(): 단건 조회
    • 결과가 없으면: null
    • 결과가 둘 이상이면: com.querydsl.core.NonUniqueResultException
  • fetchFirst(): limit(1).fetchOne()와 같다.
  • fetchResults(): 페이징 정보 포함, total count 쿼리 추가 실행
  • fetchCount(): count 쿼리로 변경해서 count 수 조회
//List
List<Member> fetch = queryFactory
        .selectFrom(member)
        .fetch();
        
//단건
Member findMember1 = queryFactory
        .selectFrom(member)
        .fetchOne();
        
//처음 한 건 조회
Member findMember2 = queryFactory
        .selectFrom(member)
        .fetchFirst();
        
//페이징에서 사용
QueryResults<Member> results = queryFactory
        .selectFrom(member)
        .fetchResults();
        
//count 쿼리로 변경
long count = queryFactory
        .selectFrom(member)
        .fetchCount();

[ 정렬 ]

/**
 * 회원 정렬 순서
 * 1. 회원 나이 내림차순(desc)
 * 2. 회원 이름 올림차순(asc)
 * 단 2에서 회원 이름이 없으면 마지막에 출력(nulls last)
 */
@Test
public void sort() {
    em.persist(new Member(null, 100));
    em.persist(new Member("member5", 100));
    em.persist(new Member("member6", 100));
    
    List<Member> result = queryFactory
            .selectFrom(member)
            .where(member.age.eq(100))
            .orderBy(member.age.desc(), member.username.asc().nullsLast())
            .fetch();
            
    Member member5 = result.get(0);
    Member member6 = result.get(1);
    Member memberNull = result.get(2);
    assertThat(member5.getUsername()).isEqualTo("member5");
    assertThat(member6.getUsername()).isEqualTo("member6");
    assertThat(memberNull.getUsername()).isNull();
}
  • desc(), asc(): 일반 정렬
    • desc(): 내림차순
    • asc(): 오름차순
  • nullsLast(), nullsFirst(): null 데이터 순서 부여

[ 페이징 ]

1. 조회 건수 제한

@Test
public void paging1() {
    List<Member> result = queryFactory
            .selectFrom(member)
            .orderBy(member.username.desc())
            .offset(1) //0부터 시작(zero index)
            .limit(2) //최대 2건 조회
            .fetch();
            
    assertThat(result.size()).isEqualTo(2);
}

2. 전체 조회 수가 필요한 경우

@Test
public void paging2() {
    QueryResults<Member> queryResults = queryFactory
            .selectFrom(member)
            .orderBy(member.username.desc())
            .offset(1)
            .limit(2)
            .fetchResults();
            
    assertThat(queryResults.getTotal()).isEqualTo(4);
    assertThat(queryResults.getLimit()).isEqualTo(2);
    assertThat(queryResults.getOffset()).isEqualTo(1);
    assertThat(queryResults.getResults().size()).isEqualTo(2);
}
  • count 쿼리에 조인이 필요 없는 성능 최적화가 필요한 경우,
    count 전용 쿼리를 별도로 작성해야 한다!
profile
🚧 https://coji.tistory.com/ 🏠

0개의 댓글