JAVA

[Querydsl] 02. 기본 문법 (Q-Type,검색조건쿼리,결과조회,정렬,페이징,집합)

코린이s 2024. 1. 28. 15:39
728x90

이전 작성글에 이어서 진행해보도록 하자!

이번 시간에는 querydsl 기본 문법에 대해 알아볼예정이다!

우선 기본 데이터 생성하는 부분을 @BeforeEach 어노테이션을 달아서 테스트 코드 실행전에 해당 코드가 실행 되도록 하며 JPAQueryFactory를 상단에 정의 하여 다른 테스트 코드에서도 사용 할 수 있도록 한다.

package study.querydsl.entity;

import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

@Slf4j
@Rollback(value = false)
@Transactional
@SpringBootTest
class MemberTest {

    @Autowired
    EntityManager em;

    JPAQueryFactory query;

    @BeforeEach
    public void testEntity(){
    query = new JPAQueryFactory(em);
    
        Team teamA = Team.builder()
                .name("teamA")
                .build();
        Team teamB = Team.builder()
                .name("teamB")
                .build();
        em.persist(teamA);
        em.persist(teamB);
        Member member1 = Member.builder()
                .username("member1")
                .age(10)
                .team(teamA)
                .build();
        Member member2 = Member.builder()
                .username("member2")
                .age(10)
                .team(teamA)
                .build();
        Member member3 = Member.builder()
                .username("member3")
                .age(20)
                .team(teamB)
                .build();
        Member member4 = Member.builder()
                .username("member4")
                .age(20)
                .team(teamB)
                .build();

        em.persist(member1);
        em.persist(member2);
        em.persist(member3);
        em.persist(member4);

        // 초기화
        em.flush();
        em.clear();

        List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();

        log.error("[data] => {}", members);

    }

}

이제 qeurydsl을 사용해보자!

우선 간단한 조회 기능을 알아보자!

아래와 같이 sql 문법과 비슷하게 조회 할 수 있으며 username 이 member1 이면서 age가 10인 데이터를 조회하면

@Test
    public void search(){
        query = new JPAQueryFactory(em);
        Member findMember = query.selectFrom(member)
                .where(member.username.eq("member1").and(member.age.eq(10)))
                .fetchOne();

        assertEquals(findMember.getUsername(), "member1");
    }

테스트 코드가 성공적으로 조회 됨을 알 수 있다.

eq 를 작성하여 == 조건에 대해 검색 했는데 아래와 같이 여러 문법이 있다.

문법 설명
eq("member") =="member"
eq("member").not() !="member"
ne("member") !="member"
isNotNull() != null
in(10,20) in (10,20)
notIn(10,20) not in (10,20)
between(10,20) between 10 and 20
goe(30) >= 30
gt(30) > 30
loe(30) <= 30
lt(30) < 30
like("member%") lke "member%"
contains("member") like "%member%"
startWith("member") like "member%"

이제 결과에 대한 처리 방법을 알아보자!

결과를 가져오는 방법은 여러개 있으며 아래와 같이 사용 가능하다.

fetch() 의 경우에는 여러건 조회시 사용

fetchOne() 의 경우에는 단건 조회시 사용(결과가 단건이 아닐시 오류 발생)

fetchFirst() 의 경우에는 첫번째 데이터를 가져올때 사용

fetchResults() 의 경우 페이징을 위해 사용

fetchCount() 의 경우 카운트만 조회시 사용한다.

@Test
    public void resultFetch(){
        List<Member> fetch = query.selectFrom(member)
                .fetch();
        log.error("[fetch] => {}", fetch);
        
        Member fetchOne = query.selectFrom(member)
                .where(member.username.eq("member1"))
                .fetchOne();
        log.error("[fetchOne] => {}", fetchOne);
        
        Member fetchFirst = query.selectFrom(member)
                .fetchFirst();
        log.error("[fetchFirst] => {}", fetchFirst);
        
        QueryResults<Member> results = query.selectFrom(member)
                .fetchResults();
        long totalCnt = results.getTotal();
        List<Member> content = results.getResults();
        log.error("[fetchResults] => {} | {}", totalCnt, content);
        
        long fetchCount = query.selectFrom(member)
                .fetchCount();
        log.error("[fetchCount] => {} ", fetchCount);
    }

결과를 보면 모두 정상적으로 나오는것을 확인할 수 있다.

이제 정렬에 대해서 알아보자!
데이터를 조금 더 추가해서 테스트를 진행하도록 한다.

age 가 100인 데이터를 조회한뒤 age 내림차순, username 오름 차순이면서 null 값은 가장 아래에 위치하도록 한다.

@Test
    public void sort(){
        em.persist(Member.builder().username(null).age(100).build());
        em.persist(Member.builder().username("member5").age(100).build());
        em.persist(Member.builder().username("member6").age(100).build());

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

        assertEquals(result.get(0).getUsername(), "member5");
        assertEquals(result.get(1).getUsername(), "member6");
        assertEquals(result.get(2).getUsername(), null);

    }

테스트 결과 성공이며 정렬이 정상적으로 된것을 볼 수 있다.

이제 페이징에 대해서 알아보자!

모든 데이터를 조회하며 2건의 데이터씩 2번째 페이지를 조회한다. 정렬은 username 내림차순이다.

    @Test
    public void paging(){
        List<Member> results = query.selectFrom(member)
                .orderBy(member.username.desc())
                .offset(1)
                .limit(2)
                .fetch();
        log.error("[result paging] => {}", results);
        assertEquals(results.size(), 2);
    }

결과 테스트가 통과 했으며 실제 데이터를 보면 2번째 페이지 2건의 데이터가 정상적으로 나온것을 볼 수 있다.

만약에 페이징 기능을 제공하는 fetchResults 를 이용하려면 아래와 같이 사용하면 된다.

@Test
    public void fetchResults(){
        QueryResults<Member> results = query.selectFrom(member)
                .orderBy(member.username.desc())
                .offset(1)
                .limit(2)
                .fetchResults();
        assertEquals(results.getTotal(), 4);
        assertEquals(results.getOffset(), 1);
        assertEquals(results.getLimit(), 2);
        assertEquals(results.getResults().size(), 2);
    }

동일하게 돌아가니 쿼리가 단순하다면 QueryResults 를 사용하면 편하지만 카운트 쿼리도 조회하는 쿼리와 동일하게 작성 되기 때문에 복잡한 쿼리에 대한 페이징 기능이 필요하다면 카운트 쿼리는 따로 호출하는것이 좋다.

이제 집합에 대해서 알아보자!(그룹지어서 연산하는것)

아래와 같이 count, sum, avg, max, min 연산자를 이용하면 되며 Tuple 로 받아서 가져오면 된다.

@Test
    public void aggregation(){
        List<Tuple> results = query
                .select(
                        member.count(),
                        member.age.sum(),
                        member.age.avg(),
                        member.age.max(),
                        member.age.min()
                )
                .from(member)
                .fetch();
        Tuple tuple = results.get(0);
        assertEquals(tuple.get(member.count()), 4);
        assertEquals(tuple.get(member.age.sum()), (10+10+20+20));
        assertEquals(tuple.get(member.age.avg()), (10+10+20+20)/4);
        assertEquals(tuple.get(member.age.max()), 20);
        assertEquals(tuple.get(member.age.min()), 10);
    }

테스트 정상적으로 통과 하였다.

다음은 group by 를 이용해야 하면 아래와 같이 사용 가능하다.

@Test
    public void groupby(){
        List<Tuple> result = query.select(
                    team.name,
                    member.age.avg()
                )
                .from(member)
                .join(member.team, team)
                .groupBy(team.name)
                .fetch();
        Tuple teamA = result.get(0);
        Tuple teamB = result.get(1);

        assertEquals(teamA.get(team.name), "teamA");
        assertEquals(teamA.get(member.age.avg()), 10);

        assertEquals(teamB.get(team.name), "teamB");
        assertEquals(teamB.get(member.age.avg()), 20);
    }

정상적으로 테스트 통과 하였다.

having 도 사용해서 teamname 이 teamB 만 뽑아보면 아래와 같이 having 을 사용하며

@Test
public void groupbyhaving(){
    List<Tuple> result = query.select(
                    team.name,
                    member.age.avg()
            )
            .from(member)
            .join(member.team, team)
            .groupBy(team.name)
            .having(team.name.eq("teamB"))
            .fetch();
    Tuple teamB = result.get(0);

    assertEquals(teamB.get(team.name), "teamB");
    assertEquals(teamB.get(member.age.avg()), 20);
}

정상적으로 통과 하였다.

다음 시간에는 조인, 서브쿼리, 연산 관련해서 알아보자!

끝!

728x90