본문 바로가기

백엔드/JPA

쿼리dsl - 조인과 서브쿼리

기본 조인

List<Member> team1 = qf.selectFrom(member)
                        .join(member.team, team)
                        .where(team.name.eq("team1"))
                        .fetch();

기본적인 join 쿼리를 만드는 방법이다. 만약 전혀 연관관계가 없는 경우라면 세타 조인을 쓰면 된다.

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

이건 sql 문으로 번역이 되면 from 절에 테이블을 나열한 것으로 번역된다.

세타조인 주의 사항 - 외부 조인 불가능

 

ON을 활용한 조인

ON을 활용해서 조인하게 되면 Cartesian Product처럼 N*M개의 결과가 나오는 것이 아닌  " ON 컬럼명 " 을 통해 컬럼명으로 조인 대상을 필터링 하게 된다. 또한 연관관계가 없는 엔티티를 외부 조인할때에 사용 가능하다.

컬럼명으로 조인 대상을 필터링 하는 경우

샘플 데이터

    @Test
    public void join_filter() {
        List<Tuple> res = qf.select(member, team)
                .from(member)
                .leftJoin(member.team, team).on(team.name.eq("team1"))
                .fetch();
        for (Tuple re : res) {
            System.out.println("re = " + re);
        }
    }

team1 이 아닌 이름을 가진 팀은 버리고 left join이기 때문에 모든 멤버를 가져온다.
만약 left join과 같은 외부조인이 아닌 내부조인이라면 ON이나 WHERE이나 동일하다

그러므로 inner join일 경우에는 익숙한 where를 사용하고 outer join일 경우에 ON을 사용하도록 한다. outer join에서 where를 사용하는 것은 넌센스다. from 절에서 테이블을 만들고 where에서 조건을 주는데 이미 from 절에서 left join으로 결과가 다 만들어진 상황에서 where에서 특정 조건을 주게 되면 

이렇게 될 것이다. 모든 결과가 조회된 상태에서 team1만 남기므로 Member(id=5, ~~), Team2 결과행은 빠지게된다.

 

연관관계가 없을때 외부조인하는 경우

List<Tuple> res = qf
                .select(member, team)
                .from(member)
                .leftJoin(team)
                .on(team.name.eq(member.username))
                .fetch();

연관관계가 없을때의 leftJoin은 좀 다르다. 정확히는 leftJoin 내의 인자가 달라진다.

만약 leftJoin(member.team, team) 하면 자동으로 ON 절에 team의 pk로 설정이 되는데 아래처럼 leftJoin(team)만 하게 되면 on절에 의해서만 필터링 된다.

그래서 결과는 이렇게 나올 것이다.

 

fetch join

잠깐 내용 복습

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

        Member m1 = qf.selectFrom(member)
                .where(member.username.eq("m1"))
                .fetchOne();
        System.out.println("=========");
        System.out.println("m1 = " + m1.getTeam().getName());
    }

이 테스트 코드를 실행하면 

select 쿼리
=============
m1의 team select 쿼리
m1 = team1

지연로딩으로 설정해뒀기 때문에 이런 결과가 나올 것이다. 그리고 페치 조인이란 sql을 날려서 결과를 가져올때 fetch join의 대상 엔티티까지 객체그래프를 그려서 가져오는 것이다.

페치조인 사용코드

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

        Member m1 = qf.selectFrom(member)
                .join(member.team, team).fetchJoin()
                .where(member.username.eq("m1"))
                .fetchOne();
        System.out.println("=========");
        System.out.println("m1 = " + m1.getTeam().getName());
    }

select 쿼리
=============
m1 = team1

 

서브쿼리

1. where 절 서브쿼리

QMember memberSub = new QMember("memberSub");

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

2. select 절 서브쿼리

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

3. from 절 서브쿼리

JPA의 JPQL 의 한계로 인해 from 절의 서브쿼리는 지원되지 않는다. 쿼리 dsl은 결국 jpql로 변환되기 때문에 jpql이 안되면 쿼리 dsl 도 안되는거다.

해결 방법
서브쿼리를 join으로 바꾸기, 애플리케이션에서 그냥 쿼리 2번 날리기, nativeSQL 쓰기

from 절에서 서브쿼리를 써야하는 경우는 "디비는 데이터를 가져오는 용도"로만 쓰면 거의 없앨 수 있다.

 

문자와 숫자

String s = qf.select(member.username.concat("_").concat(member.age.stringValue()))
        .from(member)
        .where(member.username.eq("m1")).fetchOne();
System.out.println("s = " + s);

concat을 사용하고 int 타입의 경우에는 stringValue 라는 함수가 제공된다. 이를 통해 간단하게 형변환을 할 수 있다.

 

활용해보기

https://www.inflearn.com/questions/14139 질문 참고해서 한번 해봤다.

QDate q2 = new QDate("qqq");
        em.persist(new Date(2019L,"A",20));
        em.persist(new Date(2019L,"B",21));
        em.persist(new Date(2019L,"C",21));
        em.persist(new Date(2020L,"D",19));
        em.persist(new Date(2020L,"E",18));

        List<Tuple> fetch = qf.select(date.name, date.value)
                .from(date)
                .where(date.value.eq(JPAExpressions.select(
                        q2.value.max()).from(q2).where(date.year.eq(q2.year))))
                .fetch();
        for (Tuple tuple : fetch) {
            System.out.println("tuple = " + tuple);
        }

연도별로 최대값을 가지는 항목을 가져오는 것이다.
2019년의 최대값은 21이니까 B랑 C를 가져오고. 2020년에는 19가 최댓값이니 D를 가져오는.. 결과는 잘 나오는듯 하다.

 

마무리

left join, full outer join에 대해서 좀 더 공부해봐야겠다. 어느 경우에 쓰는지 사실 잘 와닿지 않는다...

 

관련글

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

 

실전! Querydsl - 인프런 | 강의

Querydsl의 기초부터 실무 활용까지 한번에 해결, 본 강의는 자바 백엔드 개발의 실전 코스를 완성하는 마지막 강의 입니다. 스프링 부트와 JPA 실무 완전 정복 로드맵을 우선 확인해주세요. 로드

www.inflearn.com