N+1 문제 해결하기 (@EntityGraph 사용)
@EntityGraph란?
연관관계가 지연 로딩으로 되어있을 경우 fetch 조인을 사용하여 여러 번의 쿼리를 한 번에 해결할 수 있습니다.
@EntityGraph는 Data JPA에서 fetch 조인을 어노테이션으로 사용할 수 있도록 만들어 준 기능입니다.
N+1 발생 이유
기본적으로 @OneToMany, @ManyToMany 관계는 Lazy Loading으로 되어 있는 경우
JPA에서 @OneToMany와 @ManyToOne 관계 등을 지연 로딩(Lazy)으로 설정한 경우, 연관관계에서 종속된 엔티티는 쿼리 실행 시 select 되지 않고 proxy 객체를 만들어 엔티티가 적용시킵니다. 그 후 해당 proxy 객체를 호출할 때마다 그때 그때 select 쿼리가 실행됩니다.
EnttiyGraph 를 사용한 fetch-join
attributePaths 인자에 fetch-join 을 할 대상(필드명)을 작성하여 해당 엔티티들을 fetch-join 할 수 있습니다.
@EntityGraph(attributePaths = "Department")
List<User> findAll();
Repository 클래스 내에, attributePaths 인자에 fetch-join 을 할 대상(필드명)을 작성하여 해당 엔티티들을 fetch-join 할 수 있습니다.
User는 Department와 연관관계를 가지므로, @EntityGraph를 통해 Lazy 로딩을 Eager 로딩으로 변경 가능합니다.
프로젝트 적용기
@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Friend extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "fromUser_id", nullable = false)
private User fromUser;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "toUser_id", nullable = false)
private User toUser;
@Enumerated(EnumType.STRING)
private FriendStatus status;
public void setStatus(FriendStatus status) {
this.status = status;
}
@Builder
private Friend(User fromUser, User toUser, FriendStatus status) {
this.fromUser = fromUser;
this.toUser = toUser;
this.status = status;
}
public static Friend of(User fromUser, User toUser, FriendStatus status) {
return Friend.builder()
.fromUser(fromUser)
.toUser(toUser)
.status(status)
.build();
}
}
User 도메인은 Lazy 전략으로 되어 있어서, proxy 객체로 임시 저장을 하고, 필요할때마다 Select 문이 나갈 수 있다.
@Repository
public interface FriendRepository extends JpaRepository<Friend, Long>, FriendRepositoryCustom {
@EntityGraph(attributePaths = {"fromUser", "toUser"})
Optional<Friend> findFirstByFromUserAndToUserAndStatus(User fromUser, User toUser, FriendStatus status);
}
따라서 @EntityGraph에서 연관관계를 가지는 fromUser, toUser에 있어, EntityGraph를 적용했다.