본문 바로가기

개인적으로 공부한 것을 정리해 놓은 블로그입니다 틀린 것이 있으면 댓글 부탁 드립니다!


JPA/QueryDSL

QueryDSL 공부 4 - QueryDSL 활용2 (join문)

반응형

1. 기본 조인

 

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

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

    }

 

팀 이름이 teamA인 회원의 이름을 모두 조회하는 쿼리이다 . join() 메서드의 첫번째 파라미터로는 외래키가 되는 필드를 , 두번째 파라미터로는 조인할 엔티티의 Q - Type을 적어준다  ,inner 조인 ,outer 조인 모두 사용 가능하다.

 

(위의 코드는 모두 static import를 한 상태이기 때문에 위와 같이 사용이 가능 한 것이다 static import를 하지 않았다면 , Qteam.team, QMember.member 이런 식으로 써야함)

 

2.세타조인 

 

연관관계가 전혀없는 테이블을 조인할때 세타조인을 사용한다 DB는 cross조인을 통해 연관관계가 없는 두 테이블을 모두 조인하여 조회하게된다 . 세타 조인의 경우 외부조인이 불가능하다 .

 

   @Test
    void theta_join(){
        //멤버이름이 팀이름과 같은 회원 조회

        em.persist(new Member("teamA"));
        em.persist(new Member("teamB"));

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

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

    }

 

출력되는 쿼리

 

        select
            member0_.member_id as member_i1_0_,
            member0_.age as age2_0_,
            member0_.team_id as team_id4_0_,
            member0_.username as username3_0_ 
        from
            member member0_ cross 
        join
            team team1_ 
        where
            member0_.username=team1_.name

 

크로스 조인이 일어난다 . 

 

 

3. on절을 활용한 join

 

 

    @Test
    void join_on_filtering(){
        List<Tuple> result = qf.select(member, team)
                .from(member)
                .leftJoin(member.team, team).on(team.name.eq("teamA"))
                .fetch();
        for (Tuple tuple : result) {
            System.out.println("tuple = " + tuple);
        }
    }

 

조인문 뒤에 on()을 통해 on 절 조건을 적어주면 된다. 위 같은경우 member 와 team 을 leftjoin하여 가져오고  on절 조건으로 team의 이름이 teamA인 것만 가져오도록 했다 . leftjoin이기 떄문에 member를 기준으로 데이터를 모두 가져오고 team은 team A에 해당하는 데이터만 가져올 거다 , 만약 팀이름이 teamA가 아니라면 null로 표시될 것이다. 

  내부 조인을 사용할 경우 공통되는 부분만 데이터가 가져와지기 때문에 leftjoin에서 null로 표시되었던 데이터 레코드는 사라진다. 

이 경우 where 절로 조건을 주는 것과 결과가 같기 때문에 내부조인을 사용할 경우에는 익숙한 where 절로 조건을 주고 외부조인이 필요할 때 on 절을 활용하는 것이 좋다 . 

 

 

4. 페치조인

 

    @Test
    void fetchJoin(){

        em.flush();
        em.clear();


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

        boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());

        assertThat(loaded).as("페치조인 미적용").isTrue();
    }

 

조인문 뒤에 fetchJoin()을 달아주면 fetch 조인이 된다. fetch 조인은 필요한 데이터를 한번에 가져오고 싶을때 사용되고  쿼리에서 실행 되기 때문에  엔티티의 기본페치전략이 Lazy로 되어있어도 fetch 조인이 우선된다.  

 

emf 는 @PesistanceUnit 을 통해 EntityManagerFactory를 담은 변수이다  EntityManagerFactory의 isLoaded()를 통해 해당 필드가 로딩되있는지 상태를 알 수 있다.  지정한 데이터를 한번에 가져오는 fetch조인이기 때문에 loaded는 true가 된다. 

 

 

5.서브 쿼리

 

QueryDsl에서 서브쿼리를 사용하기 위해서는 com.querydsl.jpa.JPAExpressions를 사용해야한다 .

 

 

 

한계

- JPA JPQL 서브쿼리는 from 절의 서브쿼리는 지원하지 않는다. 

 해결 : -  from 절의 서브쿼리는 다 그런건 아니지만 대부분 join 절로 교체할 수 있다

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

          - nativeSQL을 사용한다. 

 

from 절의 서브쿼리같은 경우 보통 화면에 맞춰 데이터를 가져올 경우에 사용되는데 , DB는 데이터를 정제해서 가져오는 역할만 하고 화면에 맞추는 것은 프레젠테이션 계층에서 담당하는 것으로 방향을 맞춰가는 것이 좋다 (복잡한 쿼리를 줄일 수 있다).   

 

 

6. case문 사용

 

 

   
    @Test
    void basicCase(){
        List<String> fetch = qf
                .select(member.age
                        .when(10).then("열살")
                        .when(20).then("스무살")
                        .otherwise("기타"))
                .from(member)
                .fetch();

        for (String s : fetch) {
            System.out.println(s);
        }
    }

   
   //caseBuilder 사용
   @Test
    public void complexCase(){
        List<String> 기타 = qf
                .select(new CaseBuilder()
                        .when(member.age.between(0, 20)).then("0~20")
                        .when(member.age.between(20, 30)).then("20~30")
                        .otherwise("기타")
                )
                .from(member)
                .fetch();

        for (String s : 기타) {
            System.out.println(s);
        }

    }

 

 간단한 경우 when.then 으로 사용하고  

복잡한경우 caseBuilder를 사용해서 case문을 만든다 . 

 

case문도 화면에 맞는 쿼리를 짤때 사용하는 경우가 많다 . 위의 from절 서브쿼리와 마찬가지로 화면에 맞추는 일은 화면에서 해결하는 방향으로 진행하는 것이 좋다 .

 

 

7.상수 , 문자열 더하기

 

    @Test
    void testName(){
        List<Tuple> a = qf
                .select(member.username, Expressions.constant("A"))
                .from(member)
                .fetch();

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

 

Expressions.constant("A") 로 상수를 만들 수 있다.  

 

    @Test
    void concat(){
        String s = qf
                .select(member.username.concat("_").concat(member.age.stringValue()))
                .from(member)
                .where(member.username.eq("member1"))
                .fetchOne();
        System.out.println("s = " + s);
    }

 

문자열을 더할 경우에 concat()을 사용한다 . String type 만 가능하기 떄문에 조회하는 컬럼이 String이 아니라면 

 

stringValue()로 캐스팅 해줘야 concat이 가능하다.   enum 타입 같은 경우에도 DB에서는 알 수 없기 때문에 String value로 바꿔주는 것이 좋다 .

반응형