기본 문법

slee2·2022년 4월 10일
0

(인프런)Querydsl

목록 보기
3/7

JPQL vs Querydsl

JPQL

createQuery를 통해 직접 JPQL을 작성하여 보내는 방식이다.

  • setParameter를 통해 파라미터가 뭔지 직접 정해줘야한다.
  • 문장 입력을 잘못했을 경우에, 컴파일 시점이 아니라, 실제 동작한 시점에 오류가 발생하므로, 운영에 치명적인 문제가 발생할 수 있다.

Querydsl

JPAQueryFactory를 만들고,
QMember를 통해 접근하는 방식이다.

처음에 Gradle에서 compileQuerydsl을 실행시켜서 빌드에 QMember를 생성해야 사용이 가능하다.

  • 파라미터 설정에 eq 등 여러 메서드를 통해 설정이 가능하다.
  • select, from 등을 잘못 입력했을때, 컴파일 시점에서 오류가 발생하므로 사전에 방지가 가능하다.

또, JPAQueryFactory의 경우,

이런 식으로 사전에 미리 필드에 만들어둬도 된다.

기본 Q-Type 활용

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

이중에 가장 권장하는 방법은

이렇게 QMember.memberstatic Import로 만들고 사용하는 것이다.

이 경우, new Member("member1")이 자동으로 생성되는데,
같은 테이블을 조인해야하는 경우가 아니라면, 그냥 위와 같이 사용하면 된다.

검색 조건 쿼리

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%' 검색

==  eq
!=  ne
<   lt
>   gt
<=  le
>=  ge

자주 쓰이는 메서드들이다. 메뉴얼 보면서 사용해보자.

요런 식으로 사용한다.

and의 경우
이렇게 where안에 끊어치기도 가능하다.

결과 조회

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

정렬

페이징

paging1
offset 부터 limit까지 가져온다. 즉, 1부터 2까지 가져온다. 처음 시작은 0

paging2
getTotal() 전체 멤버수는 4명
getLimit, getOffset 처음과 끝
getResults 페이징 결과 리스트

집합

Querydsl에 있는 Tuple을 이용하여 서로 다른 타입의 결과값을 한곳에서 볼 수 있다.

하지만, 실무에서는 결과값을 Dto로 변환할 수 있는 기능을 사용하기 때문에 이 기능을 자주 쓰지는 않는다고 한다.

.from(member) : Member에서 찾는다.
.select(team.name, member.age.avg()) : Team 이름과 Member 나이의 평균을 가져올 것이다.
.join(member.team, team) : Member.teamTeam을 조인한다.
groupBy(team.name) : Team 이름으로 그룹을 만들 것이다.

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

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

아이템 중에서 1000원이 넘는거만 뽑아라.

조인

기본 조인

조인의 기본 문법은 첫 번째 파라미터에 조인 대상을 지정하고, 두 번째 파라미터에 별칭(alias)으로 사용할 Q타입을 지정하면 된다.

세타 조인

  • 연관관계가 없는 필드로 조인

Member 에서 Team 연결이 안된 대상을 찾아올 수 있다.

  • from 절에 여러 엔티티를 선택해서 세타 조인
  • 외부 조인 불가능 -> 다음에 설명할 조인 on을 사용하면 외부 조인 가능

on절

ON절을 활용한 조인(JPA 2.1부터 지원)
1. 조인 대상 필터링
2. 연관관계 없는 엔티티 외부 조인

1. 조인 대상 필터링

참고
내부조인을 할때는 where절에서 필터링 하는 것과 기능이 동일하다.
그러므로 외부조인이 필요할때는 on을 사용하고
내부조인은 where을 사용해서 해결하자.

2. 연관관계 없는 엔티티 외부 조인

  • 하이버네이트 5.1부터 on을 사용해서 서로 관계가 없는 필드로 외부 조인하는 기능이 추가되었다. 내부조인도 가능.
  • leftJoin() 부분에 일반 조인과 다르게 엔티티 하나만 들어간다.
    • 일반조인: leftJoin(member.team, team)
    • on조인: from(member).leftJoin(team).on(xxx)

페치 조인

페치 조인은 SQL에서 제공하는 기능은 아니고. SQL조인을 활용해서 연관된 엔티티를 한번에 조회하는 기능이다.

주로 성능 최적화에 이용된다.

.fetchJoin()

서브 쿼리

     /**
     * 나이가 가장 많은 회원 조회
     */
    @Test
    public void subQuery() {

        QMember memberSub = new QMember("memberSub");

        List<Member> result = queryFactory
                .selectFrom(member)
                .where(member.age.eq(
                        JPAExpressions
                                .select(memberSub.age.max())
                                .from(memberSub)
                ))
                .fetch();

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

    /**
     * 나이가 평균 이상인 회원
     */
    @Test
    public void subQueryGoe() {

        QMember memberSub = new QMember("memberSub");

        List<Member> result = queryFactory
                .selectFrom(member)
                .where(member.age.goe(
                        JPAExpressions
                                .select(memberSub.age.avg())
                                .from(memberSub)
                ))
                .fetch();

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

    /**
     * 나이가 가장 많은 회원 조회
     */
    @Test
    public void subQueryIn() {

        QMember memberSub = new QMember("memberSub");

        List<Member> result = queryFactory
                .selectFrom(member)
                .where(member.age.in(
                        JPAExpressions
                                .select(memberSub.age)
                                .from(memberSub)
                                .where(memberSub.age.gt(10))
                ))
                .fetch();

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

    @Test
    public void selectSubQuery() {

        QMember memberSub = new QMember("memberSub");

        List<Tuple> result = queryFactory
                .select(member.username,
                        JPAExpressions
                                .select(memberSub.age.avg())
                                .from(memberSub))
                .from(member)
                .fetch();

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

from 절의 서브쿼리 한계
JPA JPQL서브쿼리의 한계점은 from 절의 서브쿼리는 지원하지 않는다.
Querydsl도 지원하지 않는다.

from 절의 서브쿼리 해결방안
1. 서브쿼리를 join으로 변경한다.(가능한 상황도 있고, 불가능한 상황도 있음)
2. 어플리케이션에서 쿼리를 2번 분리해서 실행
3. nativeSQL을 사용한다.

실시간 트래픽을 생각한다면, 한방 쿼리는 안좋을 수도 있다.
설계 문제이긴한데, 엄청 복잡하게 쿼리를 짜는 것보다,
쿼리를 2번 날리는게 더 나은 선택일수도 있다.

Case 문

select, where에서 사용 가능

간단한 조건 vs 복잡한 조건

이걸 써야하나 고민을 할 수가 있다.
추천하는건 꼭 써야하는 경우가 아니라면 DB에서 해결하지 말고, 꺼낸 다음에 로직에서 해결하라.

상수, 문자 더하기

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

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

        //result
        //tuple = [member1, A]
        //tuple = [member2, A]
        //tuple = [member3, A]
        //tuple = [member4, A]
    }

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

        for (String s : result) {
            System.out.println("s = " + s);
        }

        //result
        //s = member1_10
    }

참고
member.age.stringValue() 부분이 중요한데, 문자가 아닌 다른 타입들은 stringValue()로 문자로 변환할 수 있다.
이 방법은 ENUM 처리할 때도 자주 사용한다.

0개의 댓글