Heestory

🚀 JPA 프로젝트에서 DTO(Data Transfer Object)와 어노테이션 활용하여 순환 참조 해결하기 본문

개발(0)/JPA

🚀 JPA 프로젝트에서 DTO(Data Transfer Object)와 어노테이션 활용하여 순환 참조 해결하기

까만밀가루 2025. 2. 4. 20:15

JPA를 공부하면서 순환참조에 대해 제대로 이해가 되지 않았고, 나에게도 이런 문제가 생길까? 했는데 토이 프로젝트 과정 중 떡하니 바로 생긴 순환 참조 ㅎㅎ 

 

이번 글에서는 ProductController에서 발생한 중복 데이터 반환 문제의 원인과 그 해결 방법을 알아본다.

 


✅ 1️⃣ 문제 원인 분석

 

📌 (0) 순환 참조 문제란?

양방향 관계에서 JSON 직렬화 시 무한 루프 발생

@Entity
public class Product {
    @OneToMany(mappedBy = "product", cascade = CascadeType.ALL)
    private List<Review> reviews;
}

@Entity
public class Review {
    @ManyToOne
    @JoinColumn(name = "product_id")
    private Product product;
}

📌 (1) 양방향 연관관계 순환 참조 문제

  • JPA에서는 엔티티 간의 양방향 연관관계를 설정하였고, Product와 Review의 관계가 서로 참조
  • Product → Review → Product로 순환 참조가 발생합니다.
  • 이로 인해 JSON 직렬화 시 무한 루프에 빠지면서 중복 데이터가 무한히 반환되고 있음

📌 (2) 잘못된 Fetch 전략 설정 (EAGER 로딩)

  • JPA의 기본 Fetch 전략은 LAZY이지만, EAGER로 설정 시 연관된 모든 데이터가 즉시 로딩
  • 이로 인해 불필요한 데이터까지 포함되어 API 응답이 과도하게 커지는 문제가 발생합니다.

 

 

🚀 2️⃣ 해결 방법

✅ (1) 순환 참조 방지 설정

Product.java

@OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonManagedReference  // 순환 참조 방지
private List<Review> reviews;

 

 

Review.java

@ManyToOne
@JoinColumn(name = "product_id", referencedColumnName = "id", nullable = false)
@JsonBackReference  // 순환 참조 방지
private Product product;

 

  • @JsonManagedReference: 직렬화 시 포함할 데이터로 유지
  • @JsonBackReference: 직렬화 시 제외하여 무한 루프 방지

 

 

✅ (2) DTO로 데이터 반환하기

    • DTO (Data Transfer Object): 데이터베이스의 엔티티(Entity)와 프론트엔드 간에 필요한 데이터만 전송하기 위해 사용되는 객체
    • 엔티티 객체를 그대로 반환하면 해당 엔티티와 연관된 모든 데이터가 포함되기 때문에 엔티티 대신 필요한 필드만 포함한 DTO를 사용하여 응답 데이터를 관리

ProductDTO.java

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class ProductDTO {
    private Long id;
    private String name;
    private String description;
    private double price;
    private Integer stock;

    public ProductDTO(Product product) {
        this.id = product.getId();
        this.name = product.getName();
        this.description = product.getDescription();
        this.price = product.getPrice();
        this.stock = product.getStock();
    }
}

 

 

✅ (3) 컨트롤러 수정

  • Controller에서 Product 엔티티에 들어있는 불필요한 데이터 List<Preview>가 포함되지 않도록 함

ProductController.java

@GetMapping
public ResponseEntity<List<ProductDTO>> getAllProducts() {
    List<ProductDTO> products = productService.getAllProducts().stream()
            .map(ProductDTO::new)  // Product → ProductDTO로 변환
            .toList();
    return ResponseEntity.ok(products);
}

 

 

✅ (4) 불필요한 데이터 로딩 방지 (Lazy 로딩 적용)

  • 실제로 필요할 때만 데이터를 로딩되도록 Lazy 적

Product.java

@OneToMany(mappedBy = "product", fetch = FetchType.LAZY)
private List<Review> reviews;

 

 

 

 


이렇게 하여 순환참조를 해결하였다.

엔티티 관계를 좀 더 읽고 DTO를 잘 활용하도록 해야겠다.

  •  

'개발(0) > JPA' 카테고리의 다른 글

JPA 페이징 처리  (0) 2025.02.07
[IntelliJ]Querydsl 설치  (0) 2023.08.25
[IntelliJ]JPA 프로젝트 개발시 에러  (0) 2023.08.18