1. 개요
- JPQL의 내부, 외부, 세타 조인에 대해 알아보자.
2. 내부 조인
- 연관 관계가 맺어진 엔티티들에 대한 Inner Join을 말한다.
- JPQL 작성 시 INNER JOIN의 INNER 는 생략 가능하다.
SELECT m FROM Member m [INNER] JOIN m.team t
3. 외부 조인
- 연관 관계가 맺어진 엔티티들에 대한 Left Outer Join을 말한다.
- JPQL 작성 시 LEFT OUTER JOIN의 OUTER는 생략 가능하다.
SELECT m FROM Member m LEFT [OUTER] JOIN m.team t
4. 세타 조인
- 엔티티들에 대한 조인을 말한다.
- 연관 관계와 상관 없기에 엔티티 명을 정확히 기입해야 한다.
SELECT count(m) FROM Member m, Team t WHERE m.username = t.name
5. 조인 예제
- 테스트를 위해 teamA와 teamB를 생성하였다. 멤버 0부터 9까지는 teamA, 멤버 10부터 19까지는 teamB에 속하도록 하였고, 멤버 20부터 29까지는 팀이 없도록 테스트 데이터를 세팅하였다. 데이터 셋 코드는 다음과 같다.
Team teamA = new Team();
teamA.setName("teamA");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("teamB");
em.persist(teamB);
for(int i =0 ; i< 10 ; i++){
Member member = new Member();
member.setUsername("멤버"+i);
member.setAge(i);
member.changeTeam(teamA);
em.persist(member);
}
for(int i =10 ; i< 20 ; i++){
Member member = new Member();
member.setUsername("멤버"+i);
member.setAge(i);
member.changeTeam(teamB);
em.persist(member);
}
for(int i =20 ; i< 30 ; i++){
Member member = new Member();
member.setUsername("멤버"+i);
member.setAge(i);
em.persist(member);
}
5.1. 내부 조인 예제
- 아래의 JPQL을 실행하여 내부 조인 시 team이 존재하는 Member 정보만을 리턴한다.
String innerJoinQuery = "select m from Member m inner join m.team t";
List<Member> list = em.createQuery(innerJoinQuery,Member.class)
.getResultList();
for(Member member : list){
System.out.println(member.toString());
System.out.println(member.getTeam());
}
// 출력 결과
Member{id=3, username='멤버0', age=0}
Team{id=1, name='teamA'}
Member{id=4, username='멤버1', age=1}
Team{id=1, name='teamA'}
...
Member{id=21, username='멤버18', age=18}
Team{id=2, name='teamB'}
Member{id=22, username='멤버19', age=19}
Team{id=2, name='teamB'}
5.2. 외부 조인 예제
- 아래의 JPQL을 실행하여 외부 조인 시 team이 존재하지 않는 Member 정보도 함께 리턴한다.
String leftJoinQuery = "select m from Member m left join m.team t";
List<Member> list2 = em.createQuery(leftJoinQuery,Member.class)
.getResultList();
for(Member member : list2){
System.out.println(member.toString());
System.out.println(member.getTeam());
}
// 출력 결과
Member{id=3, username='멤버0', age=0}
Team{id=1, name='teamA'}
Member{id=4, username='멤버1', age=1}
Team{id=1, name='teamA'}
...
Member{id=31, username='멤버28', age=28}
null
Member{id=32, username='멤버29', age=29}
null
5.3. 세타 조인 예제
- 테스트를 위해 member 이름이 teamA인 멤버를 생성하였다. 실제 실행된 쿼리 확인 결과, 두 테이블을 크로스 조인 후 조건에 해당하는 값만을 조회한다.
Member thetaMember = new Member();
thetaMember.setUsername("teamA");
thetaMember.changeTeam(teamA);
em.persist(thetaMember);
String thetaJoinQuery = "select m from Member m, Team t where m.username = t.name";
List<Member> list3 = em.createQuery(thetaJoinQuery,Member.class)
.getResultList();
for(Member member : list3){
System.out.println(member.toString());
System.out.println(member.getTeam());
}
// 출력 결과
Member{id=33, username='teamA', age=0}
Team{id=1, name='teamA'}
6. 조인 대상 필터링
- SQL에서 사용하던 on과 동일하게 사용한다. 내부 조인, 외부 조인도 동일한 방식으로 사용 가능하다.
// 내부 조인에 대한 필터링 - 회원과 팀을 조인하면서, 팀 이름이 A인 팀만 조인
JPQL : SELECT m, t FROM Member m LEFT JOIN m.team t ON t.name = 'A'
SQL : SELECT m.*, t.* FROM Member m LEFT JOIN Team t ON m.team_id = t.id and t.name = 'A'
// 외부 조인에 대한 필터링 - 회원의 이름과 팀 이름이 같은 대상 외부조인
JPQL : SELECT m, t FROM Member m LEFT JOIN Team t ON m.username = t.name
SQL : SELECT m.*, t.* FROM Member m LEFT JOIN Team t ON m.username = t.name
7. 회고
- JPQL에서 사용하는 조인과 SQL에서 사용하는 조인은 조인의 개념만 잘 알고있다면 어렵지 않게 적용함을 알았다. 모든 조인에 세타 조인을 사용해도 되나, 크로스 조인으로 인한 성능 저하를 고려해봤을 때 테이블의 연관관계를 확실히 이해하고 그에 따른 조인 전략을 구상하는 게 중요함을 느꼈다.
8. 참고
- JAVA ORM 표준 JPA 프로그래밍 - 김영한
'백엔드 > JPA' 카테고리의 다른 글
[JPA] ConstraintViolationException 발생 원인 / 영속성 전이 (0) | 2023.05.17 |
---|---|
[JPA] 프로젝션 및 JPA 페이징 API (0) | 2023.04.13 |
[JPA] JPQL 개념 및 기본 문법 (0) | 2023.04.12 |