[에러노트] 프록시 객체 일 경우 직렬화를 지원하지 않아 생기는 에러

hyewon jeong·2023년 7월 4일
0

에러노트

목록 보기
30/45

1. 발생

상품 조회 api 구현 후 테스트 할때 생긴 에러이다.

2. 코드

{"createdDate":null,"modifiedDate":null,"orderId":1,"nickname":
"customer","orderItems":[{"item":{"createdDate":"2023-07-
04T20:30:53.192754","modifiedDate":"2023-07-
04T20:31:10.788095","id":2,"name":"좋은
책","price":1000,"stockQuantity":8,"itemDetail":"좋은책이
야","image":"default1.png","sellerNickname":"크
롱","hibernateLazyInitializer"}}]}Type definition error: [simple 
type, class 
org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor]; nested 
exception is 
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No 
serializer found for class 
org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no 
propert

3. 원인

프록시 객체는 기본적으로 직렬화(Serialization)을 지원하지 않습니다.

주어진 오류 내용에서 알 수 있는 것은 Jackson 라이브러리가 org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor 클래스에 대한 직렬화 기능을 찾지 못한다는 것입니다.

이러한 문제는 Hibernate의 프록시 객체 때문에 발생할 수 있습니다. Hibernate는 지연로딩(Lazy loading)을 지원하기 위해 프록시 객체를 사용합니다. 하지만 이러한 프록시 객체는 직렬화할 수 없는 특성을 가지고 있습니다.

OrderItemResponse 클래스에서 Item 객체를 필드로 가지고 있습니다. 그리고 Item 객체는 프록시 객체입니다. 이 프록시 객체는 직렬화되지 않는 클래스인 org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor와 연관되어 있습니다. 그래서 직렬화 과정에서 문제가 발생하는 것입니다.

해결 방법으로는 OrderItemResponse 클래스에서 Item 필드를 프록시 객체 대신 실제 엔티티 객체로 변경해야 합니다. OrderItem 엔티티 클래스에서 item 필드의 타입을 변경한 후, 해당 타입을 OrderItemResponse 클래스에 반영해야 합니다.

기존 코드

@Getter
public class OrderItemResponse {

  private final Item item; // 한 상품을 여러번 주문 할 수 있으니 m:1

  private final int orderPrice; // 주문가격
  private final int count; //주문수량

  public OrderItemResponse(Item item, int orderPrice, int count) {
    this.item = item;
    this.orderPrice = orderPrice;
    this.count = count;
  }
}
package study.wonyshop.order.dto;

import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import lombok.Getter;
import study.wonyshop.common.TimeStamped;
import study.wonyshop.order.entity.Order;
import study.wonyshop.order.entity.OrderStatus;
import study.wonyshop.orderItem.OrderItemResponse;

@Getter
public class OrderResponse extends TimeStamped {

  private final Long orderId; // 주문번호
  private final String nickname;
  private final List<OrderItemResponse> orderItems;

  private final int totalPrice; // 총 금액
  private final OrderStatus status; // 주문 상태

  public OrderResponse(Order order) {
    this.orderId = order.getId();
    this.nickname = order.getUser().getNickname();
    this.orderItems = order.getOrderItems().stream().map(o-> new OrderItemResponse(o.getItem(),o.getOrderPrice(),o.getCount())).collect(Collectors.toList());
    this.totalPrice = order.getTotalPrice();
    this.status = order.getStatus();
  }
}

4. 해결

지연로딩으로 인한 프록시 객체인 item 을 dto에 필요한 필드만을 담아 전달함으로 직렬화가 가능 해졌다.

@Getter
public class OrderItemResponse {

  private final ItemResponse itemName; // 한 상품을 여러번 주문 할 수 있으니 m:1

  private final int orderPrice; // 주문가격
  private final int count; //주문수량

  public OrderItemResponse(String itemName, int orderPrice, int count) {
    this.itemName = new ItemResponse(itemName);
    this.orderPrice = orderPrice;
    this.count = count;
  }
}

5. 알게 된 점

프록시 객체는 영속성 컨텍스트와의 상호작용을 유지하기 위해 실제 엔티티를 참조하는 래퍼입니다. 하지만 프록시 객체는 프록시 자체의 상태와 연관된 영속성 컨텍스트의 정보를 직렬화할 수 없습니다. 때문에 프록시 객체를 직렬화하려고 시도하면 예외가 발생합니다.

프록시 객체를 직렬화해야 할 경우

프록시 초기화:

프록시 객체를 초기화하여 실제 엔티티 객체로 변환한 후 직렬화할 수 있습니다. 이 방법은 프록시 객체의 실제 데이터를 직렬화하는 것이기 때문에 주의가 필요합니다.

DTO 사용:

프록시 객체 대신 DTO(Data Transfer Object)를 사용하여 필요한 데이터를 전달하고 직렬화할 수 있습니다. DTO는 엔티티의 필요한 필드만을 포함하는 일반적으로 직렬화 가능한 객체입니다.

프록시 우회:

프록시 객체를 우회하고 엔티티 객체를 직접 사용하여 직렬화할 수 있습니다. 이 경우에는 프록시 객체를 피해서 엔티티 객체를 사용해야 합니다.
따라서, 프록시 객체의 직렬화가 필요한 경우에는 위의 방법 중 적합한 방법을 선택하여 사용해야 합니다.

프록시 객체 초기화 방법( 강제초기화 vs DTO 반환)

첫 번째 방법인 강제 초기화를 통해 프록시 객체를 사용하는 방법과 두 번째 방법인 DTO를 사용하는 방법은 각각 장단점이 있습니다. 어떤 방법이 더 좋은지는 상황과 개발자의 선호도에 따라 다를 수 있습니다.

강제 초기화를 통한 프록시 객체 사용의 장점:

코드 변경이 상대적으로 적음: 기존 코드에서 직접 프록시 객체를 사용하는 방식으로 변경하기만 하면 되므로 수정해야 할 코드 양이 적습니다.
프록시 객체를 직접 사용할 수 있음: 프록시 객체가 실제 엔티티로 초기화되므로, 엔티티의 다른 필드나 메서드에 접근할 수 있습니다.
강제 초기화를 통한 프록시 객체 사용의 단점:

성능 저하: 강제 초기화를 통해 프록시 객체를 사용하면, 프록시 객체를 초기화하기 위해 데이터베이스 쿼리가 실행되므로 성능이 저하될 수 있습니다.
객체 그래프 탐색의 문제: 만약 객체 그래프가 깊게 연결된 구조라면, 강제 초기화를 통해 모든 필드를 초기화하는 것은 번거로울 수 있습니다.
DTO 사용의 장점:

성능 개선: DTO는 필요한 필드만을 담고 있기 때문에 데이터베이스에서 가져올 필요 없는 정보를 로드하지 않아 성능이 개선될 수 있습니다.
의존성 감소: DTO를 사용하면 엔티티와의 의존성이 줄어들어 객체 간 결합도가 낮아집니다. 이는 유지보수와 테스트 용이성을 높일 수 있습니다.
DTO 사용의 단점:

코드 변경이 필요: 엔티티와 DTO 사이의 매핑 로직이 필요하므로 기존 코드를 수정해야 합니다.
추가적인 클래스와 매핑 로직의 관리: DTO 클래스와 엔티티 간의 매핑 로직을 추가로 작성하고 관리해야 합니다.
따라서, 상황에 따라 선택해야 합니다. 강제 초기화를 통한 프록시 객체 사용은 코드 변경이 적고, 프록시 객체의 다른 필드와 메서드에 접근할 수 있는 장점이 있지만 성능 저하와 객체 그래프 탐색의 문제가 있을 수 있습니다. DTO 사용은 성능 개선과 의존성 감소의 장점이 있지만 코드 변경이 필요하고 추가적인 클래스와 매핑 로직 추가로 작성하고 관리해야 합니다.

profile
개발자꿈나무

1개의 댓글

comment-user-thumbnail
2024년 1월 30일

안녕하세요!
도움이 되었습니다
취업이 너무 어렵네요ㅠ

답글 달기