12월 22일 금 TIL

장숭혁·2023년 12월 22일
0

TIL작성

목록 보기
37/60

지연로딩 ( fetchType.LAZY )

  • 해당 엔티티를 조회할 때 트랜잭션이 필수는 아니지만, 지연로딩된 엔티티를 실제로 사용할 때는 트랜잭션이 적용되어 있어야 한다.
  • 영속성 컨텍스트 기능 중 하나이다.
@Test
    @Transactional
    @DisplayName("Robbie 고객 조회")
    void test2() {
        User user = userRepository.findByName("Robbie");
        System.out.println("user.getName() = " + user.getName());

        System.out.println("Robbie가 주문한 음식 이름 조회");
        for (Food food : user.getFoodList()) {
            System.out.println(food.getName());
        }
    }
@Entity
@Getter
@Setter
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;
}

JPA 에서 N+1문제
OneToMany상황에서 지연로딩 - 엔티티를 조회할시 사용할 때까지 데이터 로딩을 미루는 현상을 말한다.
JPQL(Java Persistence Query Language) : 엔티티를 대상으로 쿼리작성
지연로딩으로 설정된 리스트는 사용하는 시점에 가져오기 위해서 프록시 객체로 갖고 있다.
지연로딩일때

for (Food food : user.getFoodList()) {
            System.out.println(food.getName());
        }
  • 이 코드의 n+1문제 시나리오
  1. Food 객체들이 이미 로드되어 있지 않다면, 각 user에 대한 Food 객체들을 가져오기 위해 별도의 SQL 쿼리가 실행된다.
    • (프록시객체는 먼저 1차 캐시 저장소에 참조데이터가 있는지 확인한 후 없으면 쿼리를 데이터 베이스에 날려서 가져온다.
    • 반복적으로 쿼리가 날라가게 된다. ->n+1문제 발생)
  2. 만약 이 코드가 여러 user 객체에 대해 반복 실행된다면, 각 user에 대해 별도의 SQL 쿼리가 실행되게 된다.

->N+1 문제를 해결하는 방법 중 하나는 '즉시 로딩(Eager Loading)' 또는 'JOIN FETCH'를 사용하는 것이다. 이 방법을 사용하면, user 객체를 로드할 때 관련된 Food 객체들도 함께 로드하므로, user 객체를 조회하는 SQL 쿼리 1번과 Food 객체를 조회하는 SQL 쿼리 1번, 총 2번의 SQL 쿼리만 실행하면 된다.

프록시 객체 : 실제 데이터를 가지고 있지 않고, 대신 실제 데이터에 대한 참조를 갖고 있는 가짜 객체이다. 이 가짜 객체는 데이터가 필요한 시점에 실제 데이터를 데이터베이스에서 가져오게 된다. 따라서 리스트가 지연로딩으로 설정되어 있다면, 프록시 객체가 해당 리스트를 갖고 있고, 실제 데이터를 사용하는 시점에 데이터베이스에서 데이터를 가져와서 채우게 된다.

그렇다면 즉시로딩에서는 N+1문제가 발생하지 않는가?
JPQL이 즉시로딩 쿼리를 만들때 연관관계가 있는 엔티티는 신경 쓰지 않고 조회 대상이 되는 Entity기준으로만 쿼리를 만든다. 그런 후 JPA는 연관된 엔티티가 있음을 확인하고 글로벌 패치 전략을 보게된다. 즉시 로딩으로 되어 있음을 확인하고 바로 조회해서 가져온다 -> N+1문제 발생

JPQL이 즉시 로딩(Eager Loading) 쿼리를 만들 때, 조회 대상이 되는 엔티티에 집중하고, 연관관계가 있는 엔티티에 대해서는 신경 쓰지 않는다는 것을 의미한다. '신경 쓰지 않는다'라는 표현은 JPQL 쿼리가 기본적으로 연관관계가 있는 엔티티를 자동으로 로딩하지 않는다는 것이다. 연관관계가 있는 엔티티를 로딩하려면, 명시적으로 JOIN FETCH 등을 사용해야 한다.

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = "user")
    private List<Food> foodList = new ArrayList<>();
}

@Entity
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;
}

-이 상황에서 JPQL을 사용해 User 엔티티를 조회하는 쿼리

String jpql = "SELECT u FROM User u";
List<User> users = entityManager.createQuery(jpql, User.class).getResultList();

-이 쿼리는 User 엔티티만을 조회하며, 연관관계가 있는 Food 엔티티는 로딩하지 않는다.

  • 해결방법
String jpql = "SELECT u FROM User u JOIN FETCH u.foodList";
List<User> users = entityManager.createQuery(jpql, User.class).getResultList();

-명시적으로 JOIN FETCH를 사용
-User 엔티티를 조회하면서, 연관관계가 있는 Food 엔티티들도 함께 로딩한다. 이렇게 하면 User 엔티티를 로딩할 때마다 별도의 쿼리로 Food 엔티티를 로딩하는 N+1 문제를 피할 수 있다.
즉시로딩 최대한 사용말고, 지연로딩 + fetch join을 써야한다.

createQuery(String jpql, Class<T> resultClass)
JPQL문자열과 결과 클래스를 인자로 받아, 쿼리를 생성한다.resultClass 인자는 쿼리의 결과타입을 지정한다.


getResultList()
createQuery를 통해 생성된 쿼리를 실제로 실행하고, 그 결과를 리스트로 반환받는다. 반환되는 리스트의 타입은 createQuery에서 지정한 resultClass에 따른다.

  • 대표적인 fetch join 문제상황 OneToMany관계에서 페이징 처리할 때

    한명의 사람이 여러개의 할일을 가지고 있는데 여러 row(열)를 사용해야 한다.
    (DB에서 1:n 관계를 표현
    한개의 엔티티가 한줄로 표현이 불가능하다.)
    이때 5명만 페이징 요청으로 가져온다.
    ->데이터베이스에서 limit을 걸었을때 -> 잘려서 나올 수 있고 1~2명만 추출될 수 있다.
    ->데이터 누락 문제
    ->fetch join과 페이징을 같이한다면
    ->fetch join한 데이터를 전부 가져온 후 인메모리(RAM, Heap Area)에 넣어놓고 가공하는 작업을 거친다.
    ->100만건일때 메모리 부하가 일어남

    해결방법 ManyToOne일때 페이징 처리를 할 것 혹은 @BatchSize()를 사용할 것
    @OneToMany(mappedBy = "parent")
    @BatchSize(size = 10)
    private List<ChildEntity> children;
  • @BatchSize(size = 10)는 Parent 엔티티의 children이라는 컬렉션을 가져올 때, 최대 10개의 엔티티를 한 번의 쿼리로 가져오라는 의미
profile
코딩 기록

0개의 댓글

관련 채용 정보