🙏내용에 대한 피드백은 언제나 환영입니다!!🙏
앞글에서 N+1문제를 해결하는 방법들에 대해서 알아보았다.
N+1문제에 대해서 공부를 하던 중 @EntityGraph에 대해서 알게 되었다.
Entity와의 Mapping구성은 이전 게시글에서와 똑같이 진행할 것이다.
테스트 구조는 아래와 같다.
<<데이터 넣기>>
@Test
public void inputData() {
for(int i = 0; i < 10; i++) {
// 10개를 DB에 저장. 1개의 팀당 1명의 멤버 저장.
Team team = new Team("팀" + i);
teamRepository.save(team);
Member member = new Member("멤버" + i);
member.setTeam(team);
memberRepository.save(member);
}
}
<<조회하기>>
@Test
public void find() {
List<Team> teams = teamRepository.findAll();
for (Team team : teams) {
System.out.println("<< " + team.getId() + "의" + team.getMembers() + "에 접근 >>");
}
}
}
fatch join을 위해 findAll 메서드를 아래와 같이 설정했다.
public interface TeamRepository extends JpaRepository<Team, Long> {
@Query("select t from Team t join fetch t.members")
List<Team> findAll();
}
아래는 조회 결과이다.
Hibernate:
select
t1_0.id,
m1_0.team_id,
m1_0.id,
m1_0.name,
t1_0.name
from
team t1_0
join
member m1_0
on t1_0.id=m1_0.team_id
<< 팀1의[com.example.demo.Entity.Member@525aadf2]에 접근 >>
<< 팀2의[com.example.demo.Entity.Member@4a4979bf]에 접근 >>
<< 팀3의[com.example.demo.Entity.Member@5702e6f3]에 접근 >>
<< 팀4의[com.example.demo.Entity.Member@2c10f9f3]에 접근 >>
<< 팀5의[com.example.demo.Entity.Member@5166fe7a]에 접근 >>
<< 팀6의[com.example.demo.Entity.Member@29f72685]에 접근 >>
<< 팀7의[com.example.demo.Entity.Member@56114349]에 접근 >>
<< 팀8의[com.example.demo.Entity.Member@4f5d587c]에 접근 >>
<< 팀9의[com.example.demo.Entity.Member@39d62e47]에 접근 >>
<< 팀10의[com.example.demo.Entity.Member@4d2bcca8]에 접근 >>
@EntityGraph를 위해 findAll 메서드를 아래와 같이 설정했다.
public interface TeamRepository extends JpaRepository<Team, Long> {
@EntityGraph(attributePaths = "members")
List<Team> findAll();
}
아래는 조회 결과이다.
Hibernate:
select
t1_0.id,
m1_0.team_id,
m1_0.id,
m1_0.name,
t1_0.name
from
team t1_0
left join
member m1_0
on t1_0.id=m1_0.team_id
<< 팀1의[com.example.demo.Entity.Member@57e83608]에 접근 >>
<< 팀2의[com.example.demo.Entity.Member@526a1b51]에 접근 >>
<< 팀3의[com.example.demo.Entity.Member@54632794]에 접근 >>
<< 팀4의[com.example.demo.Entity.Member@760f4310]에 접근 >>
<< 팀5의[com.example.demo.Entity.Member@77eb76f]에 접근 >>
<< 팀6의[com.example.demo.Entity.Member@1efa258e]에 접근 >>
<< 팀7의[com.example.demo.Entity.Member@1ae69abe]에 접근 >>
<< 팀8의[com.example.demo.Entity.Member@d319d2c]에 접근 >>
<< 팀9의[com.example.demo.Entity.Member@5add0cc0]에 접근 >>
<< 팀10의[com.example.demo.Entity.Member@6971f5f4]에 접근 >>
fetch join과 @EntityGraph의 실행 결과 중 다른 점은 결과를 가져오는 과정이다.
fetch join에서는 "join"을. @EntityGraph에서는 "left join"을 하였다.
이것은 각각 "Inner Join". "Left Outer Join"이다.
그렇다면, 이것이 무엇인가?
Inner Join은 "교집합"이라고 할 수 있다.
이것이 무슨 말이냐면, DB에서 데이터를 조회할 때 Team과 그 해당하는 Member가 들어있어야 조회가 된다는 것이다.
예를 들어 Team1 + Member1은 조회가 되지만 Team2 + null은 조회되지 않는다.
위 코드에서 Team9와 Team10의 Member를 삭제하고 실행해 보겠다.
Hibernate:
select
t1_0.id,
m1_0.team_id,
m1_0.id,
m1_0.name,
t1_0.name
from
team t1_0
join
member m1_0
on t1_0.id=m1_0.team_id
<< 팀1의[com.example.demo.Entity.Member@33da0fad]에 접근 >>
<< 팀2의[com.example.demo.Entity.Member@106e6dfe]에 접근 >>
<< 팀3의[com.example.demo.Entity.Member@31b827a6]에 접근 >>
<< 팀4의[com.example.demo.Entity.Member@52bcd7c9]에 접근 >>
<< 팀5의[com.example.demo.Entity.Member@59fa5a39]에 접근 >>
<< 팀6의[com.example.demo.Entity.Member@7a8dbbf1]에 접근 >>
<< 팀7의[com.example.demo.Entity.Member@5d8c03cc]에 접근 >>
<< 팀8의[com.example.demo.Entity.Member@6a2d0a19]에 접근 >>
결과를보면 Team은 1부터 10까지 있지만, 팀8까지밖에 출력이 안된다.
Left Outer Join은 Inner Join과 반대로 "합집합"이다.
즉, DB에서 데이터를 조회할 때 Team에 Member이 들어있지 않더라도 조회가 된다.
Inner Join과 똑같은 DB로 실행해보겠다.
Hibernate:
select
t1_0.id,
m1_0.team_id,
m1_0.id,
m1_0.name,
t1_0.name
from
team t1_0
left join
member m1_0
on t1_0.id=m1_0.team_id
<< 팀1의[com.example.demo.Entity.Member@50c99398]에 접근 >>
<< 팀2의[com.example.demo.Entity.Member@4a66949a]에 접근 >>
<< 팀3의[com.example.demo.Entity.Member@2f726cf4]에 접근 >>
<< 팀4의[com.example.demo.Entity.Member@560e1459]에 접근 >>
<< 팀5의[com.example.demo.Entity.Member@d2ca3a9]에 접근 >>
<< 팀6의[com.example.demo.Entity.Member@3a33d655]에 접근 >>
<< 팀7의[com.example.demo.Entity.Member@70c6c6a7]에 접근 >>
<< 팀8의[com.example.demo.Entity.Member@303a484e]에 접근 >>
<< 팀9의[]에 접근 >>
<< 팀10의[]에 접근 >>
Inner Join과는 다르게 Team 1~10 모두 조회되었다.
하지만, Team9와 Team10에는 Member이 null이기에 들어가있지 않은걸 볼 수 있다.
만약에 매핑하는 두 테이블 사이에 기준 열이 서로 값을 가지고 있다면 누락되지 않을 것이다.
성능을 비교하자면, Inner Join이 데이터가 없는 것은 가지고 오지 않는 교집합 역할이기에 상대적으로 성능이 좋다.
하지만, 상황에 따라 적절히 사용해야 한다.
기본적으로 fetchType.Lazy, fetchType.Eager는 static정보로 runtime에 변경할 수 없다.
@EntityGraph는 이것을 가능하게 한다.
fetchType.Lazy를 fetchType.Eager로 변경할 수 있는 것이다.
이러한 이유로 fetch join의 특징인 중복된 데이터를 들고오는 상황. 예를 들어, Team1 + Member1과 Team1 + Member2는 다르기에 Team1을 두 번 들고오게 되는 경우. (즉, Team1은 Member의 갯수만큼 불러온다.)
그래서, Distinct를 사용하지 않아도 된다.
또한, fetch join에서 일대다 관계인 경우 1개의 컬렉션까지밖에 같이 조회를 못하였는데, 이것 또한 해결할 수 있게 되었다.
하지만, fetch join과 같이 페이징 오류는 계속 발생한다.