본문 바로가기
Server/Spring

연결된 테이블 2개의 Entity(Foreign Key)

by print_soo 2024. 9. 5.

게시판이 있고 그 게시판의 댓글이 있는 서버를 구축한다고 해보자.

그럼 우선 Table을 두개 만들어야한다. Post와 Comment 테이블을 만들어보자.

 

[POST]

Column Name Data Type Description
id BIGINT 게시글 고유 ID
(Primary Key)
title VARCHAR 게시글 제목
content VARCHAR 게시글 내용
author VARCHAR 게시글 작성자

 

 

[COMMENT]

Column Name Data Type Description
id BIGINT 댓글 고유 ID
(Primary Key)
content VARCHAR 댓글 제목
author VARCHAR 댓글 작성자

 

 

이렇게 테이블을 만들면 문제가 있다. 테이블 댓글 모두 잘 만들어지지만 특정 댓글이 어떤 게시글에 포함된건지 알 수 없다. 

즉, 서로를 연결해주는 장치가 있어야한다. 

 

이때 사용하는게 외래키(Foreign Key)이다. 

외래키란? 두 테이블 간의 관계를 나타내는 열(Column)이다. 하나의 테이블에서 다른 테이블의 특정 데이터를 참조할 때 사용하는 키죠. 쉽게 말해, "이 데이터가 저 테이블의 어느 데이터와 연결되어 있어"라고 알려주는 역할

 

그래서 우리는 외래키를 Comment 테이블에 추가해줘서 Post 테이블의 ID 데이터를 참조해야한다. 

 

 

[POST]

Column Name Data Type Description
id BIGINT 게시글 고유 ID
(Primary Key)
title VARCHAR 게시글 제목
content VARCHAR 게시글 내용
author VARCHAR 게시글 작성자

 

 

[COMMENT]

Column Name Data Type Description
id BIGINT 댓글 고유 ID
(Primary Key)
content VARCHAR 댓글 제목
author VARCHAR 댓글 작성자
post_id BIGINT 해당 댓글이 달린 게시물의 ID
(Foreign Key)

 


 

이렇게 테이블을 구성하고 앤티티는 아래와 같이 구성해주어야한다. 

 

//[POST]
package noticeboard.spring_noticeboard.domain;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

import java.util.ArrayList;
import java.util.List;

@Getter
@Setter
@Entity
public class Post {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;
    private String content;
    private String author;

    @OneToMany(mappedBy = "post", cascade = CascadeType.ALL)
    private List<Comment> comments = new ArrayList<>(); //게시물에 달린 여러 댓글을 리스트로 관리

    public Post() {}

    public Post(Long id, String title, String content, String author) {
        this.id = id;
        this.title = title;
        this.content = content;
        this.author = author;
    }

    // 댓글 추가
    public void addComment(Comment comment) {
        comments.add(comment);
        comment.setPost(this);
    }


}

@OneToMany: "하나가 여러 개를 가질 수 있다"

  • what: 게시물(Post)이 여러 개의 댓글(Comment)을 가질 수 있다는 것을 나타낸다.
  • why: 데이터베이스에서는 하나의 게시물이 여러 개의 댓글과 연결될 수 있는데, 이를 객체 관계에서도 표현하기 위해 @OneToMany 어노테이션을 사용한다.

 

mappedBy = "post": 반대쪽 관계에서의 연결 설정

  • what: mappedBy는 "이 관계가 어디에 의해 관리되는지"를 나타낸다. 여기서 mappedBy = "post"Comment 엔티티에 있는 private Post post; 필드를 참조하고 있다는 뜻이다.
  • why: 두 객체(Post와 Comment)가 서로 연관된 상태에서 "누가 관계의 주인인지"를 정해야 한다.. 여기서 "주인"은 외래 키를 실제로 관리하는 쪽입니다. 댓글(Comment)에서 post라는 필드가 관계의 주인이 되며, 그 필드로 연결된 댓글들이 게시물에 달려 있다는 의미를 가진다.
  • how:
    1. mappedBy = "post"를 통해서 댓글이 어떤 게시물에 속하는지를 Comment 엔티티 안의 post 필드를 통해 알 수 있게 해준다.
    2. 이렇게 하면, 게시물(Post)이 댓글을 직접 관리하는 것이 아니라, 댓글(Comment)이 자신이 달린 게시물을 알고 있는 구조가 된다.

 

 

//[COMMENT]
package noticeboard.spring_noticeboard.domain;

import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Entity
public class Comment {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String content;
    private String author;

    public Comment() {}

    public Comment(Long id, String content, String author) {
        this.id = id;
        this.content = content;
        this.author = author;
    }

    @ManyToOne
    @JsonIgnore //양방향 관계에서 발생하는 순환 참조 문제인 JSON 직력화중 무한 루프를 방지하는 어노테이션
    @JoinColumn(name = "post_id")
    private Post post;
}

 

ManyToOne: 여러 댓글이 하나의 게시물에 속할 수 있다

  • what: 이 어노테이션은 댓글(Comment)이 여러 개일 수 있지만, 각 댓글은 반드시 하나의 게시물(Post)에 속한다는 것을 의미gks다. 즉, 여러 댓글이 하나의 게시물에 속할 수 있는 관계를 나타낸다.
  • why: 예를 들어, 하나의 게시물에 여러 사람이 댓글을 달 수 있다. 그렇다면 각 댓글은 반드시 어느 하나의 게시물에 속해야 해야한다. **@ManyToOne**을 사용해 이 구조를 표현한다.
  • easy:
    • **하나의 게시물(Post)**에 **여러 개의 댓글(Comment)**이 달릴 수 있다.
    • 하지만 **각 댓글(Comment)**은 **단 하나의 게시물(Post)**에만 속한다. 

 

@JsonIgnore: 순환 참조 문제를 해결

  • what: 이 어노테이션은 JSON 직렬화 중 발생할 수 있는 무한 루프 문제를 방지하기 위해 사용된다. 이 문제는 두 객체가 서로를 계속 참조하는 구조에서 발생할 수 있다.
  • why:
    • 게시물(Post) 객체는 **여러 댓글(Comment)**을 참조하고, 댓글 객체도 **게시물(Post)**을 참조하는 양방향 참조 관계에 있다.
    • 이런 상황에서, 만약 JSON 형식으로 데이터를 반환할 때, 게시물이 댓글을 포함하고, 댓글이 다시 게시물을 참조하면 이 참조가 끝없이 반복되면서 무한 루프가 발생할 수 있다. 
  • @JsonIgnore는 이를 방지하기 위해, 댓글 객체의 post 필드를 JSON 응답에서 무시하게 만든다. 즉, 댓글의 정보는 보내지만, 그 댓글이 어느 게시물에 속하는지는 응답에 포함되지 않도록 한다.

 

@JoinColumn(name = "post_id"): 외래 키 설정

  • what: 이 어노테이션은 댓글 테이블에서 게시물과의 관계를 설정하는 열(컬럼)을 지정한다. 즉, 댓글이 어느 게시물에 달려 있는지 표시하는 컬럼이 무엇인지 정의하는 것이다.
  • **name = "post_id"**는 댓글 테이블에서 해당 댓글이 달린 게시물의 ID를 저장하는 컬럼 이름이 post_id임을 지정합니다. 이 post_id 컬럼은 게시물 테이블의 id와 연결된다.
  • why: 댓글 테이블은 어떤 게시물에 달렸는지 알기 위해 게시물의 ID를 외래 키로 가져야 한다. 이때 **@JoinColumn(name = "post_id")**를 사용해서 댓글 테이블에서 이 외래 키가 저장될 컬럼 이름을 post_id로 지정한 것이다.
  • easy:
    • 댓글 테이블에서 각 댓글이 속한 게시물의 ID가 **post_id**라는 컬럼에 저장된다.
    • post_id 컬럼을 통해 댓글과 게시물 사이의 연결 관계를 유지할 수 있다.

 

private Post post;: 댓글이 속한 게시물

  • what: 이 필드는 댓글이 어느 **게시물(Post)**에 속해 있는지를 가리키는 필드이다.
  • why: 각 댓글은 반드시 하나의 게시물에 달려 있어야 한다. 그래서 댓글 객체는 자신이 달린 게시물을 기억해야 한다. Post post 필드를 통해 이 댓글이 속한 게시물을 참조할 수 있습니다.
  • easy: 댓글 객체는 **"내가 달린 게시물이 무엇인지"**를 알고 있어야 하고, 이를 위해 Post post 필드를 사용합니다.

 

 

post_id와 Post post의 관계

  • **post_id**는 데이터베이스에서 댓글이 어느 게시물에 속해 있는지를 숫자로 관리합니다.
  • **Post post;**는 그 post_id를 기반으로 자바 코드에서 해당 게시물 객체와 연결됩니다.
    • 즉, post_id는 데이터베이스에서 관계를 설정하고, Post post;는 그 데이터를 객체로 연결하여 댓글이 속한 게시물의 세부 정보에 접근할 수 있게 합니다.