1. JPA에서 지연 로딩(Lazy Loading)과 즉시 로딩(Eager Loading)의 차이는 무엇인가요? 어떤 상황에서 어떤 방식을 사용해야 하나요?

즉시 로딩(Eager Loading)과 지연 로딩(Lazy Loading)은 JPA에서 엔티티의 연관된 데이터를 언제 로딩할지 결정하는 방법입니다.

  1. 즉시 로딩(Eager Loading): 엔티티가 조회될 때 연관된 엔티티도 함께 조회합니다. 즉, 한 번의 쿼리로 필요한 모든 데이터를 미리 불러오는 방식입니다. JPA에서는 @ManyToOne과 @OneToOne 어노테이션에 기본적으로 적용되는 방식입니다. 즉시 로딩은 연관된 엔티티를 항상 사용할 것이 확실한 경우에 유용합니다. 하지만 불필요하게 많은 데이터를 조회하게 되면 성능 이슈를 초래할 수 있습니다. @ManyToOne(fetch = FetchType.EAGER)
  2. 지연 로딩(Lazy Loading): 엔티티를 조회할 때 연관된 엔티티는 로딩하지 않고, 실제로 연관된 엔티티를 사용할 때 로딩합니다. 즉, 실제로 사용될 때까지 데이터 로딩을 '지연'하는 방식입니다. JPA에서는 @OneToMany와 @ManyToMany 어노테이션에 기본적으로 적용되는 방식입니다. 지연 로딩은 연관된 엔티티를 항상 사용하는 것이 아닌 경우, 즉 필요한 경우에만 데이터를 조회하므로 성능 향상에 도움이 될 수 있습니다. @ManyToOne(getch = FetchType.LAZY)

  가지 방식은 각각의 상황에 따라 적절히 사용되어야 합니다. 연관된 엔티티를 항상 사용하게  경우에는 즉시 로딩을, 그렇지 않을 경우에는 지연 로딩을 사용하는 것이 일반적입니다.

더보기
  • 즉시 로딩 (Eager Loading): 엔티티가 데이터베이스에서 로드될 때, 연관된 엔티티들도 함께 로드되는 방식입니다. 이 방식을 사용하면 한 번의 쿼리로 필요한 모든 데이터를 가져올 수 있지만, 모든 연관 엔티티를 로드하므로 부담이 될 수 있습니다.
    즉시로딩은, 처음부터 모든 테이블에 조인을 걸어버리고 별도로 쿼리가 나가는 경우가 생기기에, 연관관계가 많고 복잡할수록 비용이 기하급수적으로 늘어나기에, 정확하게 이해하고 필요한 상황이 아니라면, 가급적으로 모두 지연로딩을 걸어두는게 일반적이기는 합니다. ( 쓸지 안쓸지 모르는데, 비용은 가장 많이 드는 작업일 수 있음. ) 
  • 지연 로딩 (Lazy Loading): 연관된 엔티티를 실제로 사용될 때까지 로드하지 않는 방식입니다.  방식을 사용하면 필요한 데이터만 로드하므로 메모리 사용량을 줄일  있지만, 필요한 데이터를 가져오기 위해 여러 번의 쿼리를 실행해야   있습니다.
    JPA 굳이 필요없는 DB 조회를 줄이면서 성능을 최적화한다.
    엔티티가 실제 사용될 때까지 데이터베이스 조회를 지연하는 방법을 제공하는데 이것을 지연 로딩이라 합니다. (지연 로딩 기능을 사용하려면 실제 엔티티 객체 대상에 데이터베이스 조회를 지연할  있는 가짜 객체가 필요한데 이것을 프록시 객체라고 합니다.)
    실제로 가짜 객체를 이용하면, 그때 별도의 쿼리가 나간다.특정 엔티티를 영속 상태로 만들  연관된 엔티티도 함께 영속 상태로 만들고 싶으면 영속성 전이기능을 사용하면 됩니다. JPA cascade 옵션으로 영속성 전이를 제공합니다.
  • 프록시 객체 (Proxy Object): Hibernate 나 JPA에서 많이 사용되는 개념으로, 데이터베이스에서 엔티티를 직접 가져오는 대신에 엔티티의 프록시를 생성하고, 이 프록시를 통해 엔티티에 접근합니다. 이렇게 하면 엔티티의 실제 사용 시점에만 데이터베이스에서 데이터를 가져옴으로써, 성능을 향상시킬 수 있습니다. 프록시 객체는 원래의 엔티티와 같은 인터페이스를 가지므로 사용자 코드는 원래의 엔티티와 프록시를 구분하지 않고 사용할 수 있습니다.

 

2. JPA에서 발생할 수 있는 N+1 문제는 무엇이며, 이를 해결하는 방법은 무엇인가요?

N+1 문제는 JPA나 ORM을 사용하면서 발생할 수 있는 성능 이슈입니다. N+1이라는 이름은 데이터베이스에 쿼리를 실행하는 횟수가 결과 집합의 수(N)에 비해 너무 많아진다는 것을 나타냅니다.

예를 들어, 부모 엔티티와 여러 개의 자식 엔티티가 있을 때, 부모 엔티티를 조회하는 쿼리 1번과 각 부모에 해당하는 자식 엔티티를 조회하는 쿼리 N번이 실행되면, 이를 합하면 총 N+1번의 쿼리가 실행된다는 뜻입니다. 이로 인해 성능 저하가 발생하게 됩니다.

N+1 문제를 해결하는 방법에는 주로 두 가지가 있습니다:

  1. Fetch Join: JPA의 JPQL에서 제공하는 Fetch Join을 사용하면 관련된 엔티티를 한 번의 쿼리로 함께 조회할 수 있습니다. 이를 통해 쿼리의 수를 줄일 수 있습니다.
  2. Batch Size 설정: @BatchSize 어노테이션을 사용하여 한 번의 쿼리로 가져올 엔티티의 수를 설정할 수 있습니다. 이렇게 하면 한 번의 쿼리로 더 많은 수의 엔티티를 한 번에 조회할 수 있어 N+1 문제를 완화할 수 있습니다.

 외에도 JPA 구현체(: Hibernate) 제공하는 특정 기능을 이용하는 방법도 있습니다. 예를 들어 Hibernate에서는 엔티티를 집합으로 조회하는 @Fetch(FetchMode.SUBSELECT) 같은 어노테이션을 제공합니다.

더보기

N+1 문제는 데이터를 가져올 때 발생하는 문제입니다. 만약 우리가 10명의 사용자와 그들이 쓴 게시글을 보려고 한다고 가정해 봅시다. 사용자를 먼저 찾는 쿼리를 한 번 실행하고, 각 사용자가 쓴 게시글을 찾기 위해 쿼리를 10번 더 실행한다면, 이를 모두 합치면 총 11번의 쿼리가 실행되는 것입니다. 이렇게 되면 데이터를 가져오는 데 필요한 쿼리의 횟수가 너무 많아져서 성능이 저하될 수 있습니다. 이게 바로 N+1 문제입니다.

이 문제를 해결하는 방법으로는 Fetch Join Batch Size 설정이 있습니다.

  1. Fetch Join:  방법은 사용자와 그들이  게시글을  번의 쿼리로 함께 가져오는 방법입니다. 이렇게 하면 쿼리의 횟수를 줄일  있습니다.
  2. Batch Size 설정:  방법은  번에 여러 개의 게시글을 가져오도록 설정하는 방법입니다. 예를 들어, Batch Size 5 설정하면,  번의 쿼리로 5개의 게시글을  번에 가져올  있습니다. 이렇게 하면 쿼리의 횟수를 줄일  있습니다.
  3. @Fetch(FetchMode.SUBSELECT)는 Hibernate에서 제공하는 기능으로, 특정 조건에 맞는 여러 개의 데이터(엔티티)를 한번에 가져오는 방법입니다.

    이를 좀 더 쉽게 이해하기 위해, 쇼핑몰에서 여러 개의 상품을 한 번에 볼 때를 생각해 볼까요? 각각의 상품을 하나씩 조회하는 대신, 특정 카테고리(예: '신발')의 모든 상품을 한 번에 보려고 할 것입니다. 이럴 때 @Fetch(FetchMode.SUBSELECT)를 사용하면, '신발' 카테고리의 모든 상품을 한 번의 쿼리로 가져올 수 있습니다. 이렇게 하면, 데이터베이스에 쿼리를 보내는 횟수를 줄여 성능을 향상시킬 수 있습니다.

 

 

2023.05.25 - [Spring] - JPA(Java Persistence API), ORM(Object-Relational Mapping) pt.2