양방향 Mapping이란?
양방향 매핑(Bidirectional Mapping)은 두 엔티티가 서로를 참조하는 관계를 말합니다. 이 경우,
Board
와 Reply
엔티티 간의 양방향 매핑이 설정되어 있습니다.여기서 중요한 포인트는 두 엔티티 간의 참조를 통해 서로의 데이터를 쉽게 접근하고 관리할 수 있다는 것입니다. 코드에서 양방향 매핑을 설정하는 방법은 다음과 같습니다.
- 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
}
무한루프 방지 방법
- JsonIgnore 사용:
@JsonIgnore
어노테이션을 사용하여 직렬화 시 특정 필드를 무시할 수 있습니다. 이를 통해 순환 참조를 피할 수 있습니다.
@Entity
public class A {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne(mappedBy = "a")
@JsonIgnore
private B b;
// getters and setters
}
- 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
}
- 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;
}
더미데이터 추가 :

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