Framework/Orm&Mapper

[JPA] 특정 상황에서 Lazy Fetch Type 변경하기 (@EntityGraph)

simDev1234 2022. 11. 28. 03:32

|  스프링 부트 Test 중, 아래와 같이 오류가 나타났다.

C:\sebinSample\cms\order-api\src\main\java\org\zerobase\cms\order\domain\model\Product.java:33: warning: @Builder will ignore the initializing expression entirely. If you want the initializing expression to serve as default, add @Builder.Default. If it is not supposed to be settable during building, make the field final.
    private List<ProductItem> productItems = new ArrayList();
                              ^
Note: C:\sebinSample\cms\order-api\src\main\java\org\zerobase\cms\order\domain\model\Product.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

- 테스트 코드 

@Test
void addProduct() {

    // given
    Long sellerId = 1L;

    AddProductForm form = makeProductForm("나이키 에어포스", "신발", 3);

    // when
    Product p = productService.addProduct(sellerId, form);

    // then
    Product result = productRepository.findById(p.getId()).get();

    Assertions.assertNotNull(result);
    Assertions.assertEquals(result.getSellerId(), 1L);
    Assertions.assertEquals(result.getName(), "나이키 에어포스");
    Assertions.assertEquals(result.getDescription(), "신발");
    Assertions.assertEquals(result.getProductItems().get(0).getName(), "나이키 에어포스0");
    Assertions.assertEquals(result.getProductItems().get(0).getPrice(), 10000);

}

private static AddProductForm makeProductForm(String name, String description, int itemCount) {
    List<AddProductItemForm> addProductItemForms = new ArrayList<>();
    for (int i = 0; i < itemCount; i++) {
        addProductItemForms.add(makeProductItemForm(null, name + i));
    }
    return AddProductForm.builder()
            .name(name)
            .description(description)
            .addProductItemForms(addProductItemForms)
            .build();
}

> 원인 :

- @OneToMany의 default fetch type이 LazyLoading이기 때문에, Proxy로 id 값만 담은 ProductItem들을 가져오고, 실제 내용은 들어있지 않았기 때문이다.

@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
@AuditOverride(forClass = BaseEntity.class)
@Audited  // Entity가 변할 때마다, 변화된 내용을 저장
public class Product extends BaseEntity{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Long sellerId;

    private String name;
    private String description;

    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name = "product_id")
    private List<ProductItem> productItems = new ArrayList();


    public static Product of(Long sellerId, AddProductForm form) {
        return Product.builder()
                .sellerId(sellerId)
                .name(form.getName())
                .description(form.getDescription())
                .productItems(form.getAddProductItemForms().stream()
                        .map(p -> ProductItem.of(sellerId, p)).collect(Collectors.toList())
                ).build();
    }
}

> 해결 :

이를 해결하기 위해서는 두가지 방법을 사용할 수 있는데

(1) fetch type을 EAGER로 변경한다. --> 이 방법은 그러나 불필요하게 매번 DB 조회 시 모든 데이터를 한 번에 가져오게 함으로 좋지 않다.

(2) JPA의 @EntityGraph와 findWith 함수를 통해 속성을 지정(ex. productItems)할 때, fetch type을 변경시킨다.

EntityGraphType.LOAD attributePaths가 지정된 경우 EAGER로, 지정되지 않으면 default fetch type으로
EntityGraphType.FETCH attributePaths가 지정된 경우 EAGER로, 지정되지 않으면 LAZY로
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {

    @EntityGraph(attributePaths = {"productItems"}, type = EntityGraph.EntityGraphType.LOAD)
    Optional<Product> findWithProductItemsById(Long id);

}

 

[ 출처 ]

부트캠프 수업 내용 정리