양방향 Mapping

화낼거양's avatar
Dec 04, 2024
양방향 Mapping
 
 
 

양방향 Mapping이란?

 
💡
양방향 매핑(Bidirectional Mapping)은 두 엔티티가 서로를 참조하는 관계를 말합니다. 이 경우, BoardReply 엔티티 간의 양방향 매핑이 설정되어 있습니다.
 
여기서 중요한 포인트는 두 엔티티 간의 참조를 통해 서로의 데이터를 쉽게 접근하고 관리할 수 있다는 것입니다. 코드에서 양방향 매핑을 설정하는 방법은 다음과 같습니다.
 
  • Board 엔티티:
    •  
      @OneToMany(mappedBy = "board", fetch = FetchType.EAGER) private List<Reply> replies = new ArrayList<>();
       
    • mappedBy 속성은 양방향 매핑에서 반대쪽 엔티티의 필드명을 지정합니다.
    • 이 경우 Reply 엔티티의 board 필드를 참조합니다.
    • fetch = FetchType.EAGER는 데이터를 즉시 로드하도록 설정합니다.
 
  • Reply 엔티티:
    •  
      @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "board_id") private Board board;
       
    • @ManyToOne 어노테이션을 사용하여 Board 엔티티와의 관계를 설정합니다.
    • @JoinColumn은 외래 키 컬럼을 지정합니다. 이 경우 board_id라는 컬럼을 사용합니다.
 
이렇게 양방향 매핑을 설정하면, Board 엔티티에서 Reply 목록을 접근할 수 있고, 반대로 Reply 엔티티에서 Board를 참조할 수 있게 됩니다. 이를 통해 두 엔티티 간의 관계를 쉽게 관리할 수 있습니다.
 
 
 
 

양방향 매핑의 주의점

 
 

무한루프 문제

 
무한루프는 주로 두 엔티티가 서로를 참조하고, 이를 JSON으로 직렬화할 때 발생할 수 있습니다. 예를 들어, A 엔티티가 B 엔티티를 참조하고, B 엔티티가 다시 A 엔티티를 참조할 때, 직렬화 과정에서 계속 참조를 따라가게 되어 끝나지 않는 순환이 발생할 수 있습니다.
 

예시 코드

 
// A 엔티티 @Entity public class A { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @OneToOne(mappedBy = "a") private B b; // getters and setters } // B 엔티티 @Entity public class B { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @OneToOne @JoinColumn(name = "a_id") private A a; // getters and setters }
 

무한루프 방지 방법

 
  1. JsonIgnore 사용:
      • @JsonIgnore 어노테이션을 사용하여 직렬화 시 특정 필드를 무시할 수 있습니다. 이를 통해 순환 참조를 피할 수 있습니다.
       
      @Entity public class A { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @OneToOne(mappedBy = "a") @JsonIgnore private B b; // getters and setters }
       
  1. JsonManagedReference와 JsonBackReference 사용:
      • @JsonManagedReference@JsonBackReference를 사용하여 부모-자식 관계를 명시적으로 설정할 수 있습니다.
       
      // A 엔티티 @Entity public class A { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @OneToOne(mappedBy = "a") @JsonManagedReference private B b; // getters and setters } // B 엔티티 @Entity public class B { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @OneToOne @JoinColumn(name = "a_id") @JsonBackReference private A a; // getters and setters }
       
  1. DTO 사용:
      • 엔티티 대신 Data Transfer Object(DTO)를 사용하여 순환 참조를 피할 수 있습니다. 필요한 데이터만 DTO에 담아서 전송하면 무한루프를 방지할 수 있습니다.
 
 
 
 

 
 

양방향 Mapping을 기존 코드에 적용

 
 
Reply 클래스 생성 :
@NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @Table(name = "reply_tb") @Entity public class Reply { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column(nullable = false, length = 50) private String comment; @CreationTimestamp private Timestamp createdAt; @ManyToOne(fetch = FetchType.LAZY) private User user; @ManyToOne(fetch = FetchType.LAZY) private Board board; @Builder public Reply(Integer id, String comment, Timestamp createdAt, User user, Board board) { this.id = id; this.comment = comment; this.createdAt = createdAt; this.user = user; this.board = board; } }
 
 
Board 클래스 (List<Reply> replies 추가) :
@NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @Table(name = "board_tb") @Entity // Board 객체를 테이블로 생성하며, 데이터베이스 테이블과 매핑하여 데이터베이스 작업(insert, update, delete 등)을 수행할 수 있습니다. public class Board { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) // Auto increment 와 동일 private Integer id; private String title; private String content; @ManyToOne(fetch = FetchType.LAZY) private User user; @OneToMany(mappedBy = "board", fetch = FetchType.EAGER) // fk 변수명 private List<Reply> replies = new ArrayList<Reply>(); @CreationTimestamp private Timestamp createdAt; @Builder public Board(Integer id, String title, String content, User user, Timestamp createdAt) { this.id = id; this.title = title; this.content = content; this.user = user; this.createdAt = createdAt; } public void update(String title, String content) { this.title = title; this.content = content; }
 
더미데이터 추가 :
notion image
 

위 예제에서 무한 루프를 피하기 위한 주요 요소

 
  • 지연 로딩 (FetchType.LAZY):
    • Reply 클래스에서 UserBoard에 대해 FetchType.LAZY를 사용하고 있습니다. 이는 해당 엔티티가 실제로 필요할 때까지 로드되지 않음을 의미합니다. 지연 로딩은 직렬화 중에 불필요한 순환 참조를 피하는 데 도움이 됩니다.
  • 직렬화 라이브러리의 특성:
    • Jackson과 같은 JSON 직렬화 라이브러리는 지연 로딩된 프록시 객체를 직렬화하지 않으므로, 무한루프를 방지합니다. 이는 FetchType.LAZY로 설정된 관계에 대해 직렬화할 때, 실제 객체가 로드되지 않기 때문에 발생합니다.
  • 접근 수준 제어:
    • Reply 클래스와 Board 클래스 모두 생성자에 대해 @NoArgsConstructor(access = AccessLevel.PROTECTED) 어노테이션을 사용하고 있습니다. 이는 객체를 안전하게 초기화할 수 있도록 하고, 불필요한 인스턴스 생성을 방지합니다.
Share article

moohyun