기본 문법

LeeKyoungChang·2022년 5월 5일
0
post-thumbnail

Querydsl 수업을 듣고 정리한 내용입니다.

 

📚 1. 시작 - JPQL vs Querydsl

📖 A. 테스트 기본 코드

@SpringBootTest  
@Transactional  
public class QuerydslBasicTest {  
  
    @Autowired  
    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, teamA);  
        Member member4 = new Member("member4", 40, teamA);  
  
        em.persist(member1);  
        em.persist(member2);  
        em.persist(member3);  
        em.persist(member4);  
    }  
  • 이제 이 예제로 실행한다.

 

📖 B. Querydsl vs JPQL

@Test  
public void startJPQL() {  
    // member1를 찾아라  
    Member findMember = em.createQuery("select m from Member m where m.username = :username", 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"); // 어떤 qmember인지 이름을 준 것이다.  
  
    Member findMember = queryFactory  
            .select(m)  
            .from(m)  
            .where(m.username.eq("member1"))  // 파라미터 바인딩 처리
            .fetchOne();  
  
    assertThat(findMember.getUsername()).isEqualTo("member1");  
}
  • EntityManager로 JPAQueryFactory 생성
  • Querydsl은 JPQL 빌더
  • JPQL : 문자 (실행 시점 오류) vs Querydsl : 코드 (컴파일 시점 오류)
  • JPQL : 파라미터 바인딩 직접 vs Querydsl : 파라미터 바인딩 자동 처리
  • Querydsl은 문법을 틀렸을 경우, 컴파일 타임에 오류를 바로 잡을 수 있다.

 

📖 C. 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(em)에 달려있다.

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

 

실행 결과

스크린샷 2022-05-04 오후 6 43 06 스크린샷 2022-05-04 오후 6 43 46

 

📚 2. 기본 Q-Type 활용

📖 A. 기본 Q-Type 활용

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

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

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

 

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

스크린샷 2022-05-04 오후 6 53 15

동시성 문제를 걱정하지 않아도 된다.
이전에 실행되는 @Test before에서 queryFactory = new JPAQueryFactory(em);와 같이 구현하면된다. 구현할 시, 실행하기전 @BeforeEach 애노테이션이 적힌 메서드가 실행된다.

 

스크린샷 2022-05-04 오후 6 48 29
@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를 사용하면 간편하게 사용할 수 있다.

 

📖 B. 실행되는 JPQL보기

스크린샷 2022-05-04 오후 6 59 24

위에 작성한 소스 실행시 (member1일 때)
스크린샷 2022-05-04 오후 7 00 06

  • 첫 번째가 JPQL, 두 번째 Querydsl

 

@Test  
public void startQuerydsl3() {  
    //member1을 찾아라.  
  
    // 같은 테이블을 조인하는 경우 이와 같이 사용한다.  
    QMember m1 = new QMember("m1");  
  
  
    Member findMember = queryFactory  
            .select(m1)  
            .from(m1)  
            .where(m1.username.eq("member1"))  
            .fetchOne();  
  
    assertThat(findMember.getUsername()).isEqualTo("member1");  
}
  • 입력한 m1이 보인다.
  • 같은 테이블을 조인하는 경우 이와 같이 사용한다.
스크린샷 2022-05-04 오후 7 02 16

 

📚 3. 검색 조건 쿼리

✔️ 기본 검색 쿼리

@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()를 메서드 체인으로 연결할 수 있다.

 

실행 결과

스크린샷 2022-05-05 오후 1 42 21

 

💡 참고
select, fromselectFrom으로 합칠 수 있다.

 

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

 

✔️ 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 값은 무시한다. → 메서드 추출을 활용해서 동적 쿼리를 깔끔하게 만들 수 있다.

 

📚 4. 결과 조회

  • 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();

 

✔️ 페이징에서 사용

    @Test  
    public void resultFetch() { 
		 
        QueryResults<Member> results = queryFactory  
                .selectFrom(member)  
                .fetchResults();  
  
        results.getTotal();  
        List<Member> content = results.getResults();
스크린샷 2022-05-05 오후 3 08 43
  • select 문 두번 실행된다.

 

✔️ count 쿼리로 변경

    @Test  
    public void resultFetch() {  
        long total = queryFactory  
                .selectFrom(member)  
                .fetchCount();  
    }
스크린샷 2022-05-05 오후 3 11 29

 

📚 5. 정렬

/**
 * 회원 정렬 순서
 * 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 데이터 순서 부여

 

실행 결과
스크린샷 2022-05-05 오후 3 51 01

 

📚 6. 페이징

✔️ 조회 건수 제한

    @Test  
    public void paging1() {  
        List<Member> result = queryFactory  
                .selectFrom(member)  
                .orderBy(member.username.desc())  
                .offset(1)  
                .limit(2)  
                .fetch();  
  
        assertThat(result.size()).isEqualTo(2);  
    }  
}
스크린샷 2022-05-05 오후 3 55 17

 

✔️ 전체 조회 수가 필요한 경우

@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);  
}
스크린샷 2022-05-05 오후 3 58 30 스크린샷 2022-05-05 오후 3 58 50

 

💡 참고
실무에서 페이징 쿼리를 작성할 때, 데이터를 조회하는 쿼리는 여러 테이블을 조인해야 하지만, count 쿼리는 조인이 필요 없는 경우도 있다.
그런데 이렇게 자동화된 count 쿼리는 원본 쿼리와 같이 모두 조인을 해버리기 때문에 성능이 안나올 수 있다.
count 쿼리에 조인이 필요없는 성능 최적화가 필요하다면, count 전용 쿼리를 별도로 작성해야 한다.

 

profile
"야, (오류 만났어?) 너두 (해결) 할 수 있어"

0개의 댓글