직렬화(Serialization) 와 DTO

김예인·2024년 9월 23일

⚠ 에러 노트 ⚠

목록 보기
6/6

문제가 발생한 코드

// Cart.java

@Entity
public class Cart {
    @OneToMany(mappedBy = "cart")
    private List<CartItem> items;
}

// CartItem.java

@Entity
public class CartItem {
    @ManyToOne
    @JoinColumn(name = "cart_id")
    private Cart cart;
}
// CartService.java

if(!isProductInCart) {
      CartItem newCartItem = new CartItem();
      newCartItem.setProduct(product);
      newCartItem.setCart(cart);
      cartItems.add(newCartItem);
      cart.setItems(cartItems);
      cartRepository.save(cart);
      return new SaveResponseDto(cart, true, "Product added to cart successfully");
    } else return new SaveResponseDto(null, false, "Product already in cart");

cart 에 cartItem 을 담는 코드다.
잘 담아 DB 에 올바르게 저장했음에도 아래와 같은 오류가 발생했다.

java.lang.IllegalStateException: Cannot call sendError() after the response has been committed

java.lang.StackOverflowError: null

문제의 원인

클라이언트로 SaveResponseDto 의 data 필드에 cart 엔티티 객체를 담아서 보내고 있다.
이때 Cart를 직렬화하여 응답으로 보내려고 하면, 다음과 같은 일이 발생한다.

  1. Cart 객체를 직렬화할 때 CartItem 리스트를 직렬화하려고 시도
  2. 그런데 CartItem은 다시 Cart 객체를 참조하고 있으므로, CartItem을 직렬화하면서 다시 Cart를 직렬화하려고 시도
  3. 이 과정이 끝없이 반복되며 순환 참조 발생

이 문제는 직렬화 과정에서 무한 루프를 유발하고, 메모리 초과나 예외(StackOverflowError)가 발생할 수 있다.


잠깐! 자바 직렬화?

자바 프로그램 안에서의 객체를 다른 네트워크를 통해 다른 컴퓨터로 보내거나, DB나 하드웨어에 데이터를 저장하려면 컴퓨터가 처리할 수 있는 바이트 단위로 변환해야 하는데 이를 직렬화라고 한다. 직렬화된 데이터를 원래대로 복원하는 것을 역직렬화라고 한다.

- 값 형식 데이터 : integer, float, charactor 등의 기본 타입
- Object 형식 데이터 : 메모리 주소값을 가진 데이터

자바에서는 값의 입출력을 스트림이라는 데이터 통로를 이용한다. 스트림은 바이트 단위만 이동이 가능하다.

  • 직렬화 : 자바 객체를 바이트 스트림으로 변환하여 저장하거나 네트워크로 전송하는 과정
  • 역직렬화 : 그 바이트 스트림을 다시 자바 객체로 복원하는 과정

해결

  1. @JsonIgnore 사용
@JsonIgnore
private Cart cart;  // 순환 참조가 생기는 필드에 대해 무시 처리
  1. DTO 사용
    DTO 는 엔티티의 일부 데이터만을 담고 있으며, 양방향 관계 등을 포함하지 않고 직렬화하므로 순환 참조 문제를 피할 수 있다!
profile
백엔드 개발자 김예인입니다.

0개의 댓글