Spring/팀스파르타

40. 즉시 로딩, 지연 로딩과 영속성 컨텍스트

열심히 해 2024. 12. 6. 14:55

지연 로딩과 즉시 로딩이란 무엇일까?

 

 

음식 테이블과 고객 테이블이 N : 1 양방향 관계라 가정해보겠습니다.

 

 

초기 데이터 넣기

@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의 정보를 바로 가져올지, 필요할 때 가져올지 정할 수 있습니다.
    • Fetch Type: 연관관계가 설정된 Entity의 정보-데이터를 가져오는 방법
    • Fetch Type의 종류에는 2가지가 있는데 하나는 *LAZY*, 다른 하나는 *EAGER* 입니다.
    • *LAZY*는 지연 로딩으로 필요한 시점에 정보를 가져옵니다.
    • *EAGER*는 즉시 로딩으로 이름의 뜻처럼 조회할 때 연관된 모든 Entity의 정보를 즉시 가져옵니다.
  • @OneToMany 애너테이션은 Fetch Type의 default 값이 LAZY로 지정되어있고, 반대로 @ManyToOne 애너테이션은 *EAGER로 되어있습니다.*
  • 다른 연관관계 애너테이션들도 default 값이 있는데 이를 구분하는 방법이 있습니다.
    • 애너테이션 이름에서 뒤쪽에 Many가 붙어있으면 설정된 해당 필드가 Java 컬렉션 타입일 것입니다.
      • 즉 해당 Entity의 정보가 여러 개 들어있을 수 있다는 것을 의미합니다.
      • 따라서 효율적으로 정보를 조회하기 위해 지연 로딩이 default로 설정되어있습니다.
    • 반대로 이름 뒤쪽이 One일 경우 해당 Entity 정보가 한 개만 들어오기 때문에 즉시 정보를 가져와도 무리가 없어 즉시 로딩이 default로 설정되어있습니다.

 

 

@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이 수행되었습니다.

 

 


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

 

 

 

FetchType의 default 값이 Eager 인 @ManyToOne 에너테이션의 설정을 LAZY로 바꾸고 test1() 을 실행시키면 Join 하지 않고 쿼리가 나눠 날아갈 것 같습니다. 하지만 실행 결과 에러가 발생합니다.

 

  • 영속성 컨텍스트의 기능 
    1. 1차 캐시
    2. 쓰기 지연 저장소
    3. 변경 감지
    4. 지연 로딩 
  • 지연 로딩도 마찬가지로 영속성 컨텍스트의 기능 중 하나입니다.
    • 따라서 지연 로딩된 Entity의 정보를 조회하려고 할 때는 반드시 영속성 컨텍스트가 존재해야 합니다.
    • ‘영속성 컨텍스트가 존재해야한다’라는 의미는 결국 ‘트랜잭션이 적용되어있어야 한다’라는 의미와 동일합니다.

 

@Test
@DisplayName("Robbie 고객 조회 실패")  // 지연 로딩 시 필요한 @Transactional 이 없음
void test3() {
    User user = userRepository.findByName("Robbie");

    System.out.println(user.getName() + "가 주문한 음식 이름 조회");
    for (Food food : user.getFoodList()) {
        System.out.println(food.getName());
    }
}

 

 

같은 원리로 test1()에 @Transactional 에너테이션을 달아주면 쿼리가 2번 날아가면서 데이터를 제대로 가져올 수 있습니다.

 

'Spring > 팀스파르타' 카테고리의 다른 글

42. 고아 Entity 삭제  (0) 2024.12.06
41. 영속성 전이  (0) 2024.12.06
39. Entity 연관관계 - N 대 M  (0) 2024.12.06
38. Entity 연관관계 - 1 대 N  (0) 2024.12.06
37. Entity 연관관계 - N 대 1  (0) 2024.12.06