Yebali

Join의 종류와 QueryDsl 본문

Spring

Join의 종류와 QueryDsl

예발이 2021. 11. 1. 23:35

데이터 베이스는 다양한 테이블에 데이터를 나누어 담는다.

우리는 필요에 따라 각 테이블에 있는 데이터를 조합하여 하나의 데이터 셋으로 만들어야 하는 경우가 빈번히 있다.

 

Join의 종류와 해당 Join방법들을 QueryDsl을 이용해 구현하는 방법을 알아보자.

 

Entity

예시 Entity는 'Team'과 'Member'를 사용할 것이며, 1:N 관계이다.

@Entity
class Team (
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long,
    val name: String,
    @OneToMany(mappedBy = "team")
    val members: MutableList<Member> = mutableListOf(),
)

@Entity
class Member(
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long,
    val name: String,
    @ManyToOne
    @JoinColumn(name = "team_id")
    var team: Team?
)

Test data

테스트에 사용할 데이터는 아래와 같다.

Team은 team_A, team_B, team_C가 있다.

 

Member는 member_1~6이 있으며, 

member_1, member_2는 Team_A에

member_3, member_4는 Team_B에 속하고 

member_5, member_6은 Team에 속하지 않았다.

@BeforeEach
fun init() {
  val teamA = Team.create("team_A")
  val teamB = Team.create("team_B")
  val teamC = Team.create("team_C")

  teamRepository.save(teamA)
  teamRepository.save(teamB)
  teamRepository.save(teamC)

  memberRepository.save(Member.create("member_1", teamA))
  memberRepository.save(Member.create("member_2", teamA))
  memberRepository.save(Member.create("member_3", teamB))
  memberRepository.save(Member.create("member_4", teamB))
  memberRepository.save(Member.create("member_5", null))
  memberRepository.save(Member.create("member_6", null))
}

 

Inner Join

Inner Join은 교집합( A ∩ B ) 연산과 같다. 조인 키 값이 양쪽 테이블에 모두 존재하는 데이터만 결과 데이터로 나타난다.

 

QueryDsl을 이용한 inner join구현은 아래와 같다.

fun innerJoin(): MutableList<Member> {
    return jpaQueryFactory.selectFrom(QMember.member)
        .join(QMember.member.team, QTeam.team).fetchJoin()
        .fetch()
}

실제 쿼리는 아래와 같이 나간다.

select
    member0_.id as id1_0_0_,
    team1_.id as id1_1_1_,
    member0_.name as name2_0_0_,
    member0_.team_id as team_id3_0_0_,
    team1_.name as name2_1_1_ 
from
    member member0_ 
inner join
    team team1_ 
        on member0_.team_id=team1_.id

조회된 결과는 아래와 같다.

member = member_1 / team = team_A
member = member_2 / team = team_A
member = member_3 / team = team_B
member = member_4 / team = team_B

Left Join

Left Join은 교집합 연산 결과와 차집합 연산 결과를 합친 것( (A ∩ B) ∪ (A - B) )과 같다.

조인 키 값이 양쪽에 공통적으로 존재하는 테이터와 왼쪽에 명시된 테이블에만 존재하는 데이터가 결과 데이터로 나타난다.

QueryDsl을 이용한 left join구현은 아래와 같다.

fun leftJoin(): MutableList<Member> {
    return jpaQueryFactory.selectFrom(QMember.member)
        .leftJoin(QMember.member.team, QTeam.team).fetchJoin()
        .fetch()
}

실제 쿼리는 아래와 같이 나간다.

select
    member0_.id as id1_0_0_,
    team1_.id as id1_1_1_,
    member0_.name as name2_0_0_,
    member0_.team_id as team_id3_0_0_,
    team1_.name as name2_1_1_ 
from
    member member0_ 
left outer join
    team team1_ 
        on member0_.team_id=team1_.id

조회된 결과는 아래와 같다.

member = member_1 / team = team_A
member = member_2 / team = team_A
member = member_3 / team = team_B
member = member_4 / team = team_B
member = member_5 / team = null
member = member_6 / team = null

Right Join

Right join도 교집합 연산 결과와 차집합 연산 결과를 합친 것( (A ∩ B) ∪ (A - B) )과 같다. 단지 차집합의 기준이 Left join과 반대이다.

QueryDsl을 이용한 inner join구현은 아래와 같다.

fun rightJoin(): MutableList<Member> {
    return jpaQueryFactory.selectFrom(QMember.member)
        .rightJoin(QMember.member.team, QTeam.team).fetchJoin()
        .fetch()
}

실제 쿼리는 아래와 같이 나간다.

select
    member0_.id as id1_0_0_,
    team1_.id as id1_1_1_,
    member0_.name as name2_0_0_,
    member0_.team_id as team_id3_0_0_,
    team1_.name as name2_1_1_ 
from
    member member0_ 
right outer join
    team team1_ 
        on member0_.team_id=team1_.id

조회된 결과는 아래와 같다.

member = member_1 / team = team_A
member = member_2 / team = team_A
member = member_3 / team = team_B
member = member_4 / team = team_B
member = null / team = null

member를 기준으로 right join을 했기 때문에 사실 inner join과 같게 나온다.

하지만 위의 Query를 직접 실행하면 아래와 같이 team_C까지 조회되는 것을 알 수 있다.

team_C까지 조회 된다.

 

Outer Join

OUTER JOIN은 조인하는 여러 테이블에서 한쪽에는 데이터가 있고, 한 쪽에는 데이터가 없는 경우, 데이터가 있는 쪽 테이블의 내용을 모두 출력하는 것이다.

즉, 조건에 맞지 않아도 해당하는 행을 출력하고 싶을 때 사용한다.

'Spring' 카테고리의 다른 글

[Spring] 테스트에서 Static 메소드 Mocking하기  (0) 2022.09.24
Spring의 Filter와 Interceptor  (0) 2022.01.23
QueryDSL in Kotlin  (0) 2021.10.31
Spring JPA의 Cache  (0) 2021.10.31
Spring JPA에 기본 생성자가 필요한 이유  (0) 2021.10.31