거누의 개발노트
[항해] 주특기 2주차 회고 본문
JPA에서는 데이터를 조회할 때 즉시 로딩(EAGER)과 지연 로딩(LAZY) 두 가지 방식이 있다.
이 두 가지 방식을 간단하게 설명하면 즉시 로딩은 데이터를 조회할 때 연관된 데이터까지 한 번에 불러오는 것이고, 지연 로딩은 필요한 시점에 연관된 데이터를 불러오는 것이라고 할 수 있다.
즉시로딩(EAGAL)
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String username;
@ManyToOne(fetch = FetchType.EAGER) //Team, 즉시로딩
@JoinColumn(name = "team_id")
Team team;
}
여기서 Member 객체를 조회한다면,
//멤버를 조회하는 쿼리
select
member0_.id as id1_0_,
member0_.team_id as team_id3_0_,
member0_.username as username2_0_
from
Member member0_
//팀을 조회하는 쿼리
select
team0_.id as id1_3_0_,
team0_.name as name2_3_0_
from
Team team0_
where
team0_.id=?
다음과 같이 즉시 로딩(EAGER) 방식을 사용하면 Member를 조회하는 시점에 바로 Team까지 불러오는 쿼리를 날려 한꺼번에 데이터를 불러오는 것을 볼 수 있다.
지연 로딩(LAZY)
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String username;
@ManyToOne(fetch = FetchType.LAZY)// Team, 지연로딩
@JoinColumn(name = "team_id")
Team team;
}
여기서 Member 객체를 조회한다면,
//Member만 조회
select
member0_.id as id1_0_,
member0_.team_id as team_id3_0_,
member0_.username as username2_0_
from
Member member0_
지연 로딩을 사용하면 Member를 조회하는 시점이 아닌 실제 Team을 사용하는 시점에 쿼리가 나가도록 할 수 있다는 장점이 있다.
위 예제의 즉시 로딩에서는 Member와 연관된 Team이 1개여서 Team을 조회하는 쿼리가 1개 나갔지만, 만약 Member를 조회하는 메소드를 사용했는데 연관된 Team이 1000개라면? Member를 조회하는 쿼리를 하나 날렸을 뿐인데 Team을 조회하는 SQL 쿼리 1000개가 추가로 나가게 된다.
그렇기 때문에 가급적이면 기본적으로 지연 로딩을 사용하는 것이 좋다.
참고로 fetch의 디폴트 값은 @xxToOne에서는 EAGER, @xxToMany에서는 LAZY이다.
N+1 문제
위에서 설명하는것 처럼 EAGER를 사용해서 Member를 조회하려고 할 때, Team이 1000개가 있다면 Member를 조회하려고 했을 때, 쿼리가 1000+1개를 날릴것이다. 이것이 N+1문제이다.
하지만 지연 로딩을 사용한다고 해서 N+1문제가 사라지는것은 아니다.
지연 로딩도 결론적으로는 Team을 찾으려고 할 때, 1000개를 호출해야한다.
그래서 이문제를 해결하려면 Fetch join을 사용하는 것이다. 하지만 이는 jpaRepository에서 제공해주는 것은 아니고 JPQL로 작성해야 한다.
Fetch join
@Query("select m from Member m join fetch m.team")
List<Member> findAllJoinFetch();
EntityGraph
@EntityGraph(attributePaths = "team")
@Query("select m from Member m")
List<Member> findAllEntityGraph();
@EntityGraph 의 attributePaths에 쿼리 수행시 바로 가져올 필드명을 지정하면 Lazy가 아닌 Eager 조회로 가져오게 된다.
Fetch join과 동일하게 JPQL을 사용하여 query 문을 작성하고 필요한 연관관계를 EntityGraph에 설정하면 된다. 그리고 Fetch join과는 다르게 join 문이 outer join으로 실행되는 것을 확인할 수 있다.
테이블 join을 해오기 때문에 중복된 데이터가 나올 수 있는데,
QueryDSL 사용해보기
// QueryDSL로 구현한 예제
return from(member.leftJoin(member.teams, team)
.fetchJoin()
public interface PostCustomRepository {
List<Post> findAllInnerFetchJoin();
List<Post> findAllInnerFetchJoinWithDistinct();
}
@Repository
public class PostCustomRepositoryImpl implements PostCustomRepository {
private final JPAQueryFactory jpaQueryFactory;
public PostCustomRepositoryImpl(JPAQueryFactory jpaQueryFactory) {
this.jpaQueryFactory = jpaQueryFactory;
}
@Override
public List<Post> findAllInnerFetchJoin() {
return jpaQueryFactory.selectFrom(post)
.innerJoin(post.comments)
.fetchJoin()
.fetch();
}
@Override
public List<Post> findAllInnerFetchJoinWithDistinct() {
return jpaQueryFactory.selectFrom(post)
.distinct()
.innerJoin(post.comments)
.fetchJoin()
.fetch();
}
}
회고
프레임워크와 라이브러리의 차이의 중요한 키워드는 강제성이라는걸 알게되었다.
프레임워크를 사용한다면 프레임워크에 맞는 규칙이나 패턴에 맞추어야하는것이고, 라이브러리는 자유롭게 사용할 수 있다.
@RestControllerAdvice를 이용해서 예외를 처리해 봤다.
RestFul, 연관관계, N+1 문제, 로그인 방식, JWT, JPA, Spring Dta JPA...
일주일 동안 많은 걸 배우고 오류도 많이 내봤다.
오류를 해결해나가면서 배운것도 많지만 아직 모호하고 확실하지 않은 부분을 집고 넘어가기에는 시간이 부족한 감이 있다.
실전 프로젝트까지 앞으로 일주일 남았다.
실전프로젝트를 잘 하기 위해서 앞으로 더 준비하자
이번주는 블로그에 정리할 부분을 깃에 정리해서 블로그 작성을 거의 못했다. ㅠ.ㅠ
https://github.com/geonoo/magazine
'항해' 카테고리의 다른 글
[항해] 주특기 1주차 회고 (0) | 2022.06.13 |
---|