반응형
Fetch Join 의 한계
1. Fetch Join에 선언된 Entity에 대해서 별칭 불가
개요
- JPA 표준 스펙에서는 Fetch Join 대상에 별칭이 없습니다
- 하지만 Hibernate에서 제공
Fetch Join에 별칭 선언시 이슈
- 이유: DB와 Entity 데이터의 일관성이 깨지는 이슈가 발생
- code
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(of = {"id", "name"})
public class Team {
@Id
@GeneratedValue
@Column(name = "team_id")
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private final List<Member> members = new ArrayList<>();
public Team(String name) {
this.name = name;
}
}
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(of = {"id", "username", "age"})
public class Member {
@Id
@GeneratedValue
@Column(name = "member_id")
private Long id;
private String username;
private int age;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;
public Member(String username, int age) {
this(username, age, null);
}
public Member(String username) {
this(username, 0);
}
public Member(String username, int age, Team team) {
this.username = username;
this.age = age;
if (team != null) {
changeTeam(team);
}
}
public void changeTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
}
@SpringBootTest
@Transactional
public class FetchJoinAliasTest {
@Autowired
EntityManager em;
@Test
public void contextLoads() {
Team team = new Team("teamA");
em.persist(team);
Member member1 = new Member("m1");
member1.changeTeam(team);
em.persist(member1);
Member member2 = new Member("m2");
member2.changeTeam(team);
em.persist(member2);
em.flush();
em.clear();
List<Team> testResult1 = em.createQuery("SELECT t FROM Team t JOIN FETCH t.members m WHERE m.username = 'm1'", Team.class)
.getResultList();
for (Team team1 : testResult1) {
System.out.println("team1 = " + team1.getName());
List<Member> members = team1.getMembers();
for (Member member : members) {
System.out.println("member = " + member.getUsername());
}
}
}
}
- Entity 구조
- Member 와 Team은 ManyToOne으로 연관관계 설정
- N:1관계를 맺고 있습니다
- 테스트 데이터 설명
- teamA는 m1, m2이름을 가진 Member를 2개 가지고 DB에서도 m1, m2 username을 가지고있는 Member가 teamA을 reference key를 가지고 있습니다
- teamA를 조회하면 무조건 List Collection에 2개의 데이터가 존재 해야 합니다
- 만약 m1을 가지는 member가 존재하는 Team을 출력할 경우 teamA와 member1, member2 가 같이 호출이 되게 됩니다
- m1을 가지고 있는 team을 재외할 경우 아무것도 조회가 되면 아됩니다.
- result1 조회 결과
- where 절에
m1
이라는member
를 조회 하는 쿼리 입니다 - 조회 결과 teamA는 정상적으로 조회, member는 2개가 아니라 1개만(
m1
만 조회) 조회- teamA는 2개의 Member를 가지지만 조회 결과 1개만 나옴 -> DB불일치 발생
- 영속성 컨택스트에서는 한 요청에 대해서만 처리가 되지만 만약 2차 캐시를 사용할 경우 문제가 됩니다
- 2차 캐시에서는 teamA를 저장할 경우 연관관계인 m1 member Id나 Entity가 같이 저장하고 조회시 member 를 조회하는 형태로 진해이 됩니다
- 이럴경우 다시 다른 요청에서 teamA를 조회할 경우 m1만 조회가 되는 이슈가 발생해서 데이터 불일치가 발생합니다.
- where 절에
2. 둘 이상 Collection Fetch Join 불가
- Team Member
- Team Sponser
- 위와 같은 구조를 가지고
- Team과 Member, Sponser 모두 fetch join 할경우
MultipleBagFetchException
이 발생합니다 - 이 경우 카타시안 곱 N * M이라는 큰 성능 이슈를 초래하는 버그가 발생하기 때문에 JPA에서 에러를 뛰우느 것입니다.
- 이 경우 Batch Size를 사용해서 문제를 해결할 수 있습니다.
3. Page API 성능, 메모리 이슈
Team
에 대해서 pagination 한다 하지만 member가 1개 초과할 경우 동일한 Team이 join된 member수 만큼 반복해서 표시가 될것이다.- 결론적으로 pagination을 하기 위해서 모든 Team데이터 조회하고 동일하지 않은 Team을 page 개수만큼 뽑아서 반환을 하는 방식을 취하게 됩니다
- 만약 Team이 1000만개면 1000만개를 조회하고 메모리에 올라가게 됩니다 -> 성는이슈, 메모리 에러 발생
4. 결론
- OneToMany일 경우 fetch join 말고 default batch size나 in query를 사용해서 따로 조회 하는것이 더 효과적입니다
반응형
'JPA > ORM 표준 JPA' 카테고리의 다른 글
Atomic Query 작성 (0) | 2022.04.28 |
---|---|
06 연관관계 Mapping 종류 (2) | 2021.08.21 |
05 연관관계 (0) | 2021.07.28 |
04 Entity (3) | 2021.07.08 |
03. 영속성 (0) | 2021.06.30 |