본문 바로가기
관리자

Programming-[Backend]/JPA

[Querydsl]3. 기본 문법 - 서브쿼리, Case, 상수 문자 더하기

728x90
반응형

 

1. 서브쿼리

 

 

서브 쿼리의 필요성과 SQL 사용 권장 사항

서브쿼리를 사용하면 SQL의 결과를 한 번에 깔끔하게 가져올 수 있다. 그러나 SQL로 모든 데이터를 한번에 가져오고, 깔끔하게 정리까지 할려는 것은 과한 측면이 있다.

 

SQL문 하나에 모든 데이터를 가져오도록 하지말고, 작은 SQL문으로 쪼개어 애플리케이션이나 화면단에서 데이터를 합치자. 너무 방대한 쿼리문을 작성하는 것보다 여러 개의 의미있는 SQL문을 작성하는 것이 재사용성과 가독성을 높이는데 도움을 줄 수 있다. 다만, 성능이 매우 중요하고 실시간으로 데이터를 빠르게 받아와야 하는 경우에는 쿼리 횟수를 줄여서 성능을 확보하는 것이 중요할 수도 있다.

 

raw 데이터를 가져오는 방식으로 SQL문을 작성해야한다. SQL문에서 제공하는 기본적인 함수는 사용하되, 서브쿼리나 집계함수를 너무 복잡하게 사용하는 것은 지양해야 한다. 데이터베이스에서 데이터를 가져오는데 집중하고, 데이터를 가공하거나 정리하는 기능은 애플리케이션 또는 화면단으로 넘기는 것이 바람직하다.

 

 

where 절 서브쿼리

서브쿼리를 이용하기 위해서 querydsl 에서는 JPAExpressions를 사용한다. 이를 이용하여 where 절에 서브쿼리를 적용하는 것은 아래와 같이 할 수 있다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
  @DisplayName("나이가 평균 이상인 회원 구하기")
  void subQueryGoe() throws Exception {
 
    QMember memberSub = new QMember("memberSub");
 
    List<Member> result = jpaQueryFactory
            .selectFrom(member)
            .where(member.age.goe(
                    JPAExpressions
                            .select(memberSub.age.avg())
                            .from(memberSub)
            ))
            .fetch();
 
    assertThat(result).extracting("age")
            .containsExactly(3040);
 
  }
cs

 

*부가적으로 알아둘 부분

 

같은 엔티티를 2번 조회할 땐 Q객체 새로 생성

전체 쿼리문에서 member 엔티티를 조회하고, 서브 쿼리절에서 따로 member 엔티티를 다시 한 번 조회하기 위해서 서브쿼리용 QMember 객체를 새로 생성하였다. 여기서 적용한 "memberSub" 라는 alias로 member 엔티티를 조회한다.

 

테스트 시 extracting, containsExactly 기능 활용

결과에서 특정 필드값을 뽑아내서 확인하고 싶을 땐 extracting을 사용한다. 그리고 결과에 특정 값들을 포함하고 있는지 확인하기 위해서 containsExactly 메서드를 사용했다. 모든 값이 포함되어야만 테스트가 통과된다. 예를들어 위 예제에서 30, 40이 아니라 40만 입력한다면 테스트는 실패하게 된다.

 

 

 

 

Select절 서브 쿼리

select절 서브 쿼리는 아래와 같이 사용하면 된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
  @DisplayName("각 회원의 이름과 전체 회원 평균 나이를 나열")
  void selectSubQuery() throws Exception {
    QMember memberSub = new QMember("memberSub");
 
    List<Tuple> result = jpaQueryFactory
            .select(member.username,
                    JPAExpressions
                            .select(memberSub.age.avg())
                            .from(memberSub)
            )
            .from(member)
            .fetch();
 
    for (Tuple tuple : result) {
      System.out.println("tuple = " + tuple);
    }
 
  }
cs

 

 

 

From절 서브 쿼리

JPQL 서브쿼리의 한계점으로, 아직까지는 지원하지 않는다. 강의에서는 아마 하이버네이트 버전이 올라가게 되면 지원할 수도 있을 것 같다고 한다.

 

from절 서브쿼리 해결방안

1. 서브 쿼리를 join으로 변경한다.

from절에 서브쿼리로 어떤 데이터 집합을 불러오고 싶은 상황이지만, 가능하다면 join + where 등으로 해결하는 것이 좋다.

2. 애플리케이션에서 쿼리를 2번 분리해서 실행한다.

3. native SQL을 사용한다.

 

 


 

2. Case문

 

Case문은 select, where절에 복잡한 조건 등을 추가하여 결과에 반영할 수 있는 방법이다. 그러나 DB에서 가져온 데이터를 raw로 보여주는 것이 아니라 데이터를 변경해서 보여주는 기능이기 때문에 반드시 필요한 경우만 사용하도록 한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
  void basicCase() throws Exception {
    List<String> result = jpaQueryFactory
            .select(member.age
                    .when(10).then("열살")
                    .when(20).then("스무살")
                    .otherwise("기타")
            )
            .from(member)
            .fetch();
 
    for (String s : result) {
      System.out.println("s = " + s);
    }
 
  }
cs

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
  void complexCase() throws Exception {
    List<String> result = jpaQueryFactory
            .select(new CaseBuilder()
                    .when(member.age.between(020)).then("0~20살")
                    .when(member.age.between(2130)).then("21~30살")
                    .otherwise("기타")
            )
            .from(member)
            .fetch();
 
    for (String s : result) {
      System.out.println("s = " + s);
    }
 
  }
cs

 

 

 


 

3. 상수, 문자열 처리

 

상수는 Expressions.constant를 사용하면 된다.

 

1
2
3
4
5
6
7
8
9
10
11
@Test
  void constant() throws Exception {
    List<Tuple> result = jpaQueryFactory
            .select(member.username, Expressions.constant("A"))
            .from(member)
            .fetch();
    for (Tuple tuple : result) {
      System.out.println("tuple = " + tuple);
    }
 
  }
cs

 

 

문자 붙이기는 자바 코드처럼 .concat()을 사용한다. string 끼리만 concat이 가능하므로 int value인 age에는 .stringValue() 처리를 해주었다.

 

1
2
3
4
5
6
7
8
9
10
11
12
@Test
  void concat() throws Exception {
    List<String> result = jpaQueryFactory
            .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);
    }
 
  }
cs

 

 


 

참조

 

1. 인프런_실전! Querydsl_김영한 님 강의

https://www.inflearn.com/course/Querydsl-%EC%8B%A4%EC%A0%84/dashboard

728x90
반응형