본문 바로가기

Spring Boot

[스프링부트 JPA] 연관관계 매핑

연관관계 매핑

엔티티들은 대부분 서로 관계를 맺고 있다

예를 들어 게시글 엔티티와 댓글 엔티티가 있을 때, 게시글은 여러 개의 댓글을 가질 수 있다

이렇게 엔티티들이 서로 어떤 연관관계가 있는지 파악하는것은 매우 중요하다

 

연관관계 매핑이란 객체의 참조와 테이블의 외래키를 매핑하는 것을 의미한다

JPA에서는 JDBC(Mybatis)를 사용했을 때와 달리 연관관계에 있는 상대 테이블의 PK를 멤버변수로 갖지 않고, 엔티티 객체 자체를 통째로 참조한다

 

 

JPA와 Mybatis의 차이

 

// Mybatis

private Integer post;



// JPA

private Post post;

 

위에 예제는 댓글 엔티티게시글 엔티티를 참조하는 모습이다

Mybatis는 관계에 있는 테이블의 PK를 멤버 변수로 갖지만, JPA는 관계에 있는 엔티티 객체를 참조하고 있다

물론, 단순히 참조하는 것만으로는 연관관계를 맺을 수 없다

매핑하는 방법을 알아보기전에 사용될 용어들을 살펴보자!

 

 

 

용어

1. 방향

단방향 관계: 두 엔티티가 관계를 맺을 때, 한쪽의 엔티티만 참조하고 있는 것을 의미한다

양방향 관계: 두 엔티티가 관계를 맺을 때, 양쪽이 서로 참조하고 있는 것을 의미한다

 

데이터 모델링에서는 관계를 맺어주기만 하면 자동으로 양방향 관계가 되어서 서로 참조하지만, 객체지향 모델링에서는 구현하고자 하는 서비스에 따라 단방향 관계인지, 양방향 관계인지 적절한 선택을 해야 한다

어느 정도의 비즈니스에서는 단방향 관계만으로도 해결이 가능하기 때문에 양방향 관계를 꼭 해야 하는 것은 아니다

사실 양방향 관계란 서로 다른 단방향 연관관계 2개를 애플리케이션 로직으로 잘 묶어서 양방향인 것처럼 보이게 할 뿐, 양방향 연관 관계는 존재하지 않는다고 할 수 있다

 

2. 다중성

관계에 있는 두 엔티티는 다음 중 하나의 관계를 갖는다

 

Many To One: 다대일 (N : 1)

One To Many: 일대다 (1 : N)

One To One: 일대일 (1 : 1)

Many To Many: 다대다 (N : N)

 

예를 들어 게시글은 여러 개의 댓글을 가질 수 있다. 여기서 게시글 입장에서는 댓글과 일대다 관계이며,

댓글 입장에서는 하나에 게시글에 속하므로 다대일 관계이다

즉, 어떤 엔티티를 중심으로 상대 엔티티를 바라보느냐에 따라 다중성이 다르다

 

3. 연관 관계의 주인 (Owner)

연관 관계에서 주인을 결정한다

주인을 찾는 방법은 연관관계를 갖는 두 테이블에 대해서 외래 키를 갖는 테이블이 연관관계의 주인이 된다

연관관계의 주인만이 외래키를 관리(등록, 수정, 삭제)를 할 수 있고, 반면 주인이 아닌 엔티티는 읽기만 할 수 있다

 

 

 

이 글에서는 ManyToOne, OneToMany에 대해서만 다룰 예정이다

그럼, 이제 두 엔티티가 어떻게 연관관계를 맺을 수 있는지 알아보자

 

 

1. @ManyToOne - 단방향

 

@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class Comment extends BaseTime {

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

    @Column(length = 20, nullable = false)
    private String author;

    @Column(columnDefinition = "TEXT", nullable = false)
    private String content;

    @ManyToOne // 다대일 관계, 여러 Comment가 하나의 Post에 포함
    @JoinColumn(name = "post_id") // 포함 대상 정보는 post_id에 기록
    private Post post;



@Getter 
@ToString 
@NoArgsConstructor 
@Entity 
public class Post extends BaseTime {

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

    @Column(length = 100, nullable = false) 
    private String title;

    @Column(columnDefinition = "TEXT", nullable = false) 
    private String content;

    @Column(length = 30, nullable = false)
    private String author;

 

현재 진행 중인 프로젝트에서 코드를 가져와봤다

단방향은 한쪽의 엔티티가 상대 엔티티를 참조하고 있는 상태이다

그래서 Comment(댓글) 엔티티에만 @ManyToOne 어노테이션을 추가했다

 

● @ManyToOne

@ManyToOne 어노테이션은 이름 그대로 다대일(N : 1) 관계 매핑이다

Comment 입장에서는 post와 다대일 관계 이므로 @ManyToOne이 된다

연관관계를 매핑할 때 이렇게 다중성을 나타내는 어노테이션(@ManyToOne, @OneToMany 등...)은 필수로 사용해야 하며, 엔티티 자신을 기준으로 다중성을 생각해야 한다

 

● @JoinColumn(name = "post_id")

@JoinColumn 어노테이션은 외래 키를 매핑할때 사용한다

name속성에는 매핑 할 외래 키 이름을 지정한다

Comment엔티티의 경우 Post 엔티티의 id필드를 외래 키로 가지므로, post_id를 작성하면 된다

※@JoinColumn어노테이션을 생략하면 아래와 같은 전략에 따라 외래 키를 매핑한다

필드명 + "_" + 참조하는 테이블의 기본 키(@Id) 컬럼명

 

이제 단방향 관계를 맺었으므로 외래 키가 생겼기 때문에, Comment에서 Post 정보를 쭉 가져올 수 있게 됐다

그런데 Post에서 Comment 정보들을 쭉 가져오고 싶다면 어떻게 해야 할까?

데이터 모델링에서는 1:N 관계만 설정해주면 자동으로 양방향 관계가 되기 때문에 어느 테이블에서든 join만 해주면 원하는 칼럼들을 가져올 수 있다

Mybatis에서 SQL을 직접 작성하므로 데이터 모델링을 할 때 테이블 간의 외래 키만 잘 설정해줬다면, join으로 원하는 칼럼을 가져올 수 있던 것을 떠올리면 쉽게 이해가 갈 것이다

 

JPA에는 양방향 관계를 맺음으로써 해결할 수 있다

 

 

2. @OneToMany

 

public class Post extends BaseTime {

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

    @Column(length = 100, nullable = false) 
    private String title;

    @Column(columnDefinition = "TEXT", nullable = false) 
    private String content;

    @Column(length = 30, nullable = false)
    private String author;

    //하나의 Post 여러개의 Comment를 가질 수 있다
    //@OneToMany: 일대다
    //fetch: 연결 방법 설정
    //mappedBy: comments를 연결할 테이블명 설정
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "post")
    private List<Comment> comments;

 

Post는 Comment를 List로 가지며, @OneToMany 어노테이션에 mappedBy속성을 추가했다

 

연관관계에서 주인을 정하는 방법은 mappedBy속성을 사용하는 것이다

 - 주인은 mappedBy 속성을 사용하지 않는다

 - 주인이 아니면 mappedBy 속성을 사용해서 속성의 값으로 연관관계의 주인을 정할 수 있다

 

주인은 mappedBy 속성을 사용할 수 없으므로 주인이 아닌 Post엔티티의 comments 필드에 mappedBy 속성을 사용하여 주인이 아님을 JPA에게 알려준다

@OneToMany(fetch = FetchType.LAZY, mappedBy = "post")
private List<Comment> comments;

 

말 그대로 해석해보면, Post 입장에서 많은 Comment를 가지므로 @OneToMany 어노테이션을 달아주고, comments 필드는 post에 의해 매핑되므로, mappedBy = "post" 작성한 것이다

여기서 "post"는 Comment 엔티티에서 Post엔티티를 참조할 때 작성한 필드명이다 (1. @ManyToOne, 예제 코드 참고)

사실 두 엔티티 사이에서 일대다 관계만 잘 따지면 누가 owner인지 따질 필요가 없다

즉, 데이터 모델링에서 어떤 엔티티가 외래 키를 갖고 있어야 하는지 파악한 후, 적절한 어노테이션만 추가해주면 그만

 

Comment 클래스의 코드는 똑같다

이미 단방향 관계를 맺은 상태에서 Post가 Comment 쪽으로 단방향 연결을 해주었기 때문에 양방향 관계가 성립된 것이다

또한 양방향 관계든, 단방향 관계든 테이블을 정의하는 SQL도 같다

외래 키가 Comment에 있다

 

 


간단하게 ManyToOne, OneToMany에 대해서 알아보았다

나머지 Many To Many, OneToOne에 대해서 알아보고 싶으면 여기를 참고 (본 포스팅도 여기를 참고하여 작성했음)