Querydsl - 기본 문법

Seongjin Jo·2022년 12월 28일
0

Querydsl

목록 보기
4/8

✔ 검색 조건 쿼리


    @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() 를 메서드 체인으로 연결할 수 있다.
참고 : select , from 을 selectFrom 으로 합칠 수 있음

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

2.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 조건이 추가됨

✔ 결과조회 쿼리


fetch() : 리스트 조회, 데이터 없으면 빈 리스트 반환
fetchOne() : 단 건 조회
  - 결과가 없으면 : null
  - 결과가 둘 이상이면 : com.querydsl.core.NonUniqueResultException
fetchFirst() : limit(1).fetchOne()
fetchResults() : 페이징 정보 포함, total count 쿼리 추가 실행
fetchCount() : count 쿼리로 변경해서 count 수 조회

Querydsl 5.0 버전 이상 fetchResults(), fetchCount() 메서드가 지원되지 않는다. 그렇기 때문에 명확하게 카운트 쿼리를 별도로 작성하고, fetch()를 사용해서 해결해야 한다.

✔ 정렬 쿼리


회원 정렬 순서
1.회원 나이 내림차순(sec)
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();

    }

✔ 페이징


1.조회 건수 제한

    @Test
    public void paging1(){
        List<Member> result = queryFactory
                .selectFrom(member)
                .orderBy(member.username.desc())
                .offset(1)
                .limit(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()).isEqualTo(2);
    }

위 방식은 이제 지원하지 않기 때문에 아래 방식으로 따로 토탈 쿼리를 자바 Long 타입으로 생성해야한다. 페이징 쿼리는 반환타입을 fetch()타입으로 사용하자.

        Long totalCount = queryFactory
                .select(member.count())
                .from(member)
                .fetchOne();

        assertThat(totalCount.longValue()).isEqualTo(4);

✔ 집합


    @Test
    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); //데이터 값은 1개라서
        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);
    }

✔ Group By , having


팀의 이름과 각 팀의 평균 연령을 구해라.

    @Test
    public void group(){
        List<Tuple> result = queryFactory
                .select(team.name, member.age.avg())
                .from(member)
                .join(member.team, team) //member의 team과 team 조인
                .groupBy(team.name) //team의 이름으로 그룹핑
                .fetch();

        Tuple teamA = result.get(0);
        Tuple teamB = result.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);
    }

groupBy , 그룹화된 결과를 제한하려면 having

having 예시

.groupBy(item.price)
.having(item.price.gt(1000))

✔ 조인


팀 A에 소속된 모든 회원

    @Test
    public void join(){
        List<Member> result = queryFactory
                .selectFrom(member)
                .leftJoin(member.team, team)
                .where(team.name.eq("teamA"))
                .fetch();

        assertThat(result)
                .extracting("username")
                .containsExactly("member1","member2");
    }

✔ 세타 조인


세타조인
회원의 이름이 팀 이름과 같은 회원 조회
연관관계가 없는 필드 조인

    @Test
    public void theta_join(){
        em.persist(new Member("teamA"));
        em.persist(new Member("teamB"));

        List<Member> result = queryFactory
                .select(member)
                .from(member, team)
                .where(member.username.eq(team.name))
                .fetch();

        assertThat(result)
                .extracting("username")
                .containsExactly("teamA","teamB");
    }

✔ 조인 - ON 절


조인 on절

  • 조인 대상 필터링
  • 연관관계 없는 엔티티 외부 조인

예) 회원과 팀을 조인하면서, 팀 이름이 teamA인 팀만 조인, 회원은 모두 조회
JPQL : select m,t from Member m left join m.team t on t.name = 'teamA'

@Test
    public void join_on_filtering(){
        List<Tuple> result = queryFactory
                .select(member, team)
                .from(member)
                .leftJoin(member.team, team).on(team.name.eq("teamA"))
                .fetch();

        //일반 join으로 하게되면 null은 다 걸러져서 출력되기때문에
        //where문으로 하는것이랑 같다
        //.join(member.team,team)
        //.where(team.name.eq("teamA")

        for (Tuple tuple : result) {
            System.out.println("tuple = " + tuple);
        }
    }

✔ 연관관계가 없는 외부 조인


연관관계가 없는 엔티티 외부 조인
회원의 이름이 팀 이름과 같은 대상 외부 조인

    @Test
    public void join_on_no_relation(){
        em.persist(new Member("teamA"));
        em.persist(new Member("teamB"));

        List<Tuple> result = queryFactory
                .select(member, team)
                .from(member)
                .leftJoin(team).on(member.username.eq(team.name))
                .fetch();
                //on 조인
                //원래는 .leftjoin(member.team, team) 이렇게 조인하는데 id값끼리 매칭됨
                //id 매칭이 아니고 이름으로만(member.username)=(team.name) 조인대상이 필터링된다.

        for (Tuple tuple : result) {
            System.out.println("tuple = " + tuple);
        }
    }

✔ 패치 조인


@Test
    public void fetchJoinNo(){
        em.flush();
        em.clear();

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

        boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
        assertThat(loaded).as("페치 조인 미적용").isFalse();
    }
@Test
    public void fetchJoinUse(){
        em.flush();
        em.clear();

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

        boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
        assertThat(loaded).as("페치 조인 적용").isTrue();
    }

패치 조인은 쿼리를 조인된 연관관계 객체까지 한방에 읽어와서 즉시로딩으로 여러번 쿼리를 보내지않는다.

✔ 서브 쿼리


Q객체 이름이 겹치면 안되기 때문에 Q객체를 따로 생성해준다.

1.나이가 평균 이상

 @Test
    public void subQueryGoe(){
        QMember memberSub = new QMember("memberSub");

        List<Member> result = queryFactory
                .selectFrom(member)
                .where(member.age.goe(
                        JPAExpressions //satic import 생략가능
                                .select(memberSub.age.avg())
                                .from(memberSub) // ->  30,40
                ))
                .fetch();

        assertThat(result).extracting("age")
                .containsExactly(30,40);
    }

2.서브쿼리 여러 건 처리, in 사용 / 10살 보다 많은 사람

 @Test
    public void subQueryIn() {
        QMember memberSub = new QMember("memberSub");
        List<Member> result = queryFactory
                .selectFrom(member)
                .where(member.age.in(
                                .select(memberSub.age)
                                .from(memberSub)
                                .where(memberSub.age.gt(10))
                ))
                .fetch();
        assertThat(result).extracting("age")
                .containsExactly(20, 30, 40);
    }

3.select 절에 subquery

@Test
    public void selectSubQuery(){
        QMember memberSub = new QMember("memberSub");
        List<Tuple> result = queryFactory
                .select(member.username,
                                .select(memberSub.age.avg())
                                .from(memberSub))
                .from(member)
                .fetch();
        for (Tuple tuple : result) {
            System.out.println("tuple = " + tuple);
        }
    }

jpa 서브 쿼리의 한계-> from절의 서브쿼리가 안된다. querydsl도 안된다.

해결방안

  • 서브쿼리를 join으로 변경한다.
  • 애플리케이션에서 쿼리를 2번 분리해서 실행한다.
  • nativeSQL을 사용한다.

✔ Case 문


    @Test
    public void basicCase(){
        List<String> result = queryFactory
                .select(member.age
                        .when(10).then("열살")
                        .when(20).then("스무살")
                        .otherwise("기타"))
                .from(member)
                .fetch();
    }
    @Test
    public void complexCase(){
        List<String> result = queryFactory
                .select(new CaseBuilder()
                        .when(member.age.between(0, 20)).then("0~20살")
                        .when(member.age.between(21, 30)).then("21~30살")
                        .otherwise("기타"))
                .from(member)
                .fetch();
    }

✔ 상수, 문자 더하기


@Test
    public void constant(){
        List<Tuple> result = queryFactory
                .select(member.username, Expressions.constant("A"))
                .from(member)
                .fetch();
    }
    @Test
    public void concat(){

        //{username}_{age}
        List<String> result = queryFactory
                .select(member.username.concat("_").concat(member.age.stringValue()))
                .from(member)
                .where(member.username.eq("member1"))
                .fetch();

0개의 댓글