스프링 - JPA 지연로딩

2024. 8. 27. 17:41JPA

2024/08/27

 

※   지연 로딩에 대해 알아보자.

 

   ▶  지연 로딩과 즉시 로딩



           ●  음식 테이블과 고객 테이블이 N : 1 양방향 관계라 가정해보자.

     ➡️ FetchTypeTest

package com.sparta.jpaadvance.fetch;

import com.sparta.jpaadvance.entity.Food;
import com.sparta.jpaadvance.entity.User;
import com.sparta.jpaadvance.repository.FoodRepository;
import com.sparta.jpaadvance.repository.UserRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;

@SpringBootTest
public class FetchTypeTest {

    @Autowired
    UserRepository userRepository;
    @Autowired
    FoodRepository foodRepository;

    @Test
    @Transactional
    @Rollback(value = false)
    void init() {
        List<User> userList = new ArrayList<>();
        User user1 = new User();
        user1.setName("Robbie");
        userList.add(user1);

        User user2 = new User();
        user2.setName("Robbert");
        userList.add(user2);
        userRepository.saveAll(userList);

        List<Food> foodList = new ArrayList<>();
        Food food1 = new Food();
        food1.setName("고구마 피자");
        food1.setPrice(30000);
        food1.setUser(user1); // 외래 키(연관 관계) 설정
        foodList.add(food1);

        Food food2 = new Food();
        food2.setName("아보카도 피자");
        food2.setPrice(50000);
        food2.setUser(user1); // 외래 키(연관 관계) 설정
        foodList.add(food2);

        Food food3 = new Food();
        food3.setName("후라이드 치킨");
        food3.setPrice(15000);
        food3.setUser(user1); // 외래 키(연관 관계) 설정
        foodList.add(food3);

        Food food4 = new Food();
        food4.setName("후라이드 치킨");
        food4.setPrice(15000);
        food4.setUser(user2); // 외래 키(연관 관계) 설정
        foodList.add(food4);

        Food food5 = new Food();
        food5.setName("고구마 피자");
        food5.setPrice(30000);
        food5.setUser(user2); // 외래 키(연관 관계) 설정
        foodList.add(food5);
        foodRepository.saveAll(foodList);
    }



}

     ➡️ 아보카도 피자 조회

@Test
@DisplayName("아보카도 피자 조회")
void test1() {
    Food food = foodRepository.findById(2L).orElseThrow(NullPointerException::new);

    System.out.println("food.getName() = " + food.getName());
    System.out.println("food.getPrice() = " + food.getPrice());

    System.out.println("아보카도 피자를 주문한 회원 정보 조회");
    System.out.println("food.getUser().getName() = " + food.getUser().getName());
}

           ● 아보카도 피자”의 가격을 조회하려고 했을 뿐인데 자동으로 JOIN 문을 사용하여
                연관관계가 설정되어있는 고객 테이블의 정보도 가져오고 있다.

           ●  JPA는 연관관계가 설정된 Entity의 정보를 바로 가져올지, 필요할 때 가져올지 정할 수 있다.
              ○  즉, 가져오는 방법을 정하게되는데 JPA에서는 Fetch Type이라 부른다.
              ○  Fetch Type의 종류에는 2가지가 있는데 하나는  LAZY , 다른 하나는  EAGER 다.
              ○  LAZY  지연 로딩 으로 필요한 시점에 정보를 가져온다.
              ○  EAGER  즉시 로딩 으로 이름의 뜻처럼 조회할 때 연관된 모든 Entity의 정보를
                 즉시 가져온다.

           ●  기본적으로 @OneToMany 애너테이션은 Fetch Type의 default 값이  LAZY  로 지정되어있고
               반대로 @ManyToOne 애너테이션은  EAGER  로 되어있다.

           ●  다른 연관관계 애너테이션들도 default 값이 있는데 이를 구분하는 방법이 있다.
              ○  애너테이션 이름에서 뒤쪽에 Many가 붙어있으면 설정된 해당 필드가
                  Java 컬렉션 타입일 것이다.
                  ▪  즉, 해당 Entity의 정보가 여러 개 들어있을 수 있다는 것을 의미한다.
                  ▪  따라서 효율적으로 정보를 조회하기 위해  지연 로딩 이 default로 설정되어있다.
              ○  반대로 이름 뒤쪽이 One일 경우 해당 Entity 정보가 한 개만 들어오기 때문에
                  즉시 정보를 가져와도 무리가 없어  즉시 로딩 이 default로 설정되어있다.

 

     ➡️ Robbie 고객 조회

@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());
    }
}

 

           ●  이번에는 Robbie 고객을 조회한 후 Robbie 고객이 주문한 음식들의 이름을 조회했다.
           ●  @OneToMany  즉, default가  지연 로딩  으로 설정되어있기 때문에 우선 고객을 조회한 후
           ●   user.getFoodList() 호출 즉, 주문한 음식의 정보가 필요한 시점에 음식 테이블에
               해당 고객 Entity의 식별자 값을 사용하여 Select SQL이 수행되었다.

 

   ▶  영속성 컨텍스트와 지연 로딩

           ●  지난 포스팅에서 영속성 컨텍스트의 기능에 대해 알아보았다.
              ○  1차 캐시
              ○  쓰기 지연 저장소
              ○  변경 감지
           ●  지연 로딩도 마찬가지로 영속성 컨텍스트의 기능 중 하나다.
              ○  따라서 지연 로딩된 Entity의 정보를 조회하려고 할 때는
                  반드시 영속성 컨텍스트가 존재해야한다.

              ○ 
영속성 컨텍스트가 존재해야한다’라는 의미는
                  결국 ‘트랜잭션이 적용되어있어야 한다’라는 의미와 동일하다.


     ➡️ Robbie 고객 조회 실패

@Test
@DisplayName("Robbie 고객 조회 실패")
void test3() {
    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());
    }
}

              ○  ‘Robbie 고객 조회 실패’ 테스트 코드를 확인해보면 @Transactional이 test3() 메서드에
                  설정되어있지 않다.

              ○  트랜잭션이 적용되지 않았기 때문에  지연 로딩 된 음식 Entity 정보들을  user.getFoodList()
                   즉, 필요한 시점에 조회하려고 하자 오류가 발생했다. 

              ○  따라서   연 로딩  된 정보를 조회하려고 할 때는 반드시 트랜잭션이 적용되어 영속성 컨텍스트가
                  존재하는지를 확인해야한다.

 

 




※ 위 이미지들은 스파르타코딩클럽에 저작권이 있으므로 무단 도용 금지 및 상업 목적으로 사용할 수 없습니다.

 

'JPA' 카테고리의 다른 글

JPA - 고아 Entity 삭제  (0) 2024.08.29