반응형
[JPA] N+1 문제 ,개념, 원인 해결(@EntityGraph, Fetch)
- 관계를 갖고 있는 엔티티안에서 조회시에 발생할 수 있는 문제
- 1개의 쿼리를 원했지만 추가적으로 N번의 쿼리가 더 발생한다.
@EntityGraph, Fetch
- 연관된 엔티티들을 SQL 한번에 조회하는 방법
- Member N : 1 Team
- Member.class
- @ManyToOne Team team
- 실무에서는 지연로딩(FetchType.LAZY)을 사용하자
- @ManyToOne Team team
- Team.class
- @OneToMay List members
- OneToMany는 Default가 Lazy전략이다
- @OneToMay List members
- Member.class
지연로딩 (LazyLoading)
Member 조회시 Team은 각자객체로 조회해놓고 team을 사용하는 시점에 SQL이 별도로 날라간다.
설명
구현코드
//Todo Lazy로딩 작용 순수 Jpa
@Test
public void findMemberLazy() throws Exception {
//given
//member1 -> teamA
//member2 -> teamB
Team teamA \= new Team("teamA");
Team teamB \= new Team("teamB");
teamRepository.save(teamA);
teamRepository.save(teamB);
memberRepository.save(new Member("member1", 10, teamA));
memberRepository.save(new Member("member2", 20, teamB));
em.flush(); // DB에 반영
em.clear(); // 영속성컨텍스트를 날림
//when
// select 해서 멤버만 가져온다.
List<Member\> members \= memberRepository.findAll();
//then
for (Member member : members) {
//System.out.println("member.getUsername() = " + member.getUsername());
System.out.println("member.getTeam() = " + member.getTeam().getClass());
//System.out.println("member.getTeam().getName() = " + member.getTeam().getName()); // team까지는 프록시라는 가짜객체 getName시 db에서 진짜객체를 가져온다.
}
}
//Jpql 페치 조인
@Query("select m from Member m left join fetch m.team")
List<Member\> findMemberFetchJoin();
//공통 메서드 오버라이드
@Override
@EntityGraph(attributePaths \= {"team"})
List<Member\> findAll();
// JPQL + 엔티티 그래프
@EntityGraph(attributePaths \= {"team"})
@Query("select m from Member m")
List<Member\> findMemberEntityGraph();
//메서드 이름으로 쿼리에서 특히 편리하다.
@EntityGraph(attributePaths \= {"team"})
List<Member\> findByUsername(String username);
raw 중복 문제
- OneToOne OneToMany는 테이블이 1:1 매핑되기 때문에 중복의 문제가 발생하지 않는다.
- 그러나 1: N 관계의 테이블에서 join을 사용하면 당연히 로우수는 증가하기에 데이터 중복의 문제가 발생한다.
- JoinFetch는 Inner Join, Entity Graph는 Outer Join이라는 차이가 있으나
- 공통적으로 카테시안 곱(Cartesian Product)이 발생하여 Member의 수만큼 Team이 중복 발생하게 됩니다.
해결방안
- 1:N 필드타입을 Set으로 선언한다.(중복을 허용하지 않기 때문)
- 순서는 보장하려면 LinkedHashSet을 사용한다.
- distinct를 사용한다.
MultipleBagFetchException
- 2개 이상의 OneToMany 테이블에 Fetch 조인 선언 했을 때
OneToOne,ManyToOne`과 같이 단일 관계의 자식 테이블에는 Fetch Join을 써도 됩니다
해결 방법
- 자식 테이블 하나에만 Fetch Join을 걸고 나머진 Lazy Loading
- 컬렉션 페치조인은 페이징 사용이 되지않는다.
- 컬렉션 페치 조인은 1개만 사용할 수 있다.
- 모든 자식 테이블을 다 Lazy Loading으로 하게 한다.
결론
- Fetch Join이 없는 자식 엔티티에 관해서는 위에서 선언한 hibernate.default_batch_fetch_size 적용으로 100~1000개의 in 쿼리로 성능을 보장한다.
- @OneToOne, @ManyToOne과 같이 1 관계의 자식 엔티티에 대해서는 모두 Fetch Join을 적용하여 한방 쿼리를 수행한다.
참조
'프로그래밍 > 스프링 자바' 카테고리의 다른 글
DTO 의 사용범위 (2) | 2023.03.04 |
---|---|
[Java] Java enum 활용 (0) | 2022.07.17 |
[스프링 시큐리티] 스프링 시큐리티 + Jwt 흐름 알기 (0) | 2021.07.12 |
TDD 방식으로 개발 예제 (Junit5) (0) | 2021.07.04 |
[Java] JVM, 자바 메모리, GC (0) | 2021.07.03 |