Spring Model/03. 연관관계 처리

04. 연관 관계 - M:N 관계

  • -

이번 시간에는 M:N 관계의 처리에 대해 살펴보자.

M:N의 관계 처리

 

M:N의 관계

M:N 관계는 양측 모두가 여러 개의 관계를 가지는 경우이다. 예를 들어 게시글과 카테고리를 생각해보면 게시글 하나가 여러 카테고리에 속해있을 수 있고 하나의 카테고리에는 여러 개의 게시글이 있을 수 있다.

Post: Category = M:N

일반적으로 DB에서는 이런 상황을 처리하기 위해 중간에 연계 테이블을 만들어서 N:1 관계로 풀어서 처리하는 것이 일반적이다.

https://goodteacher.tistory.com/466/#comment18561464

 

[DB] 다대다 관계의 문제점과 해결

이번 포스트에서는 다대다 관계의 문제점과 해결 방법에 대해서 살펴보자.  다대다 관계다대다 관계의 예DB 모델링 과정에서 다대다 관계가 나올 수 있다. 쉽게 생각할 수 있는 관계로는 학생과

goodteacher.tistory.com

 

@ManyToMany

JPA에서는 이를 처리하기 위해 @ManyToMany 애너테이션을 지원한다. 

@Target({METHOD, FIELD}) 
@Retention(RUNTIME)
public @interface ManyToMany {
    String mappedBy() default "";

    CascadeType[] cascade() default {};
    FetchType fetch() default FetchType.LAZY;
}

사용법도 매우 단순하다. 대상 엔티티 모두에게 @ManyToMany를 추가하고 주엔티티인 Post에 mappedBy 속성을 지정하면 된다.

@Entity
public class Category  extends BaseEntity{
    @Id
    @GeneratedValue(strategy = 
                      GenerationType.IDENTITY)
    private Long cno;
    private String name;

    @ManyToMany
    @Builder.Default
    @ToString.Exclude
    private List<Post> posts 
                     = new ArrayList<>();
}
@Entity
public class Post extends BaseEntity {
    @Id
    @GeneratedValue(strategy = 
                   GenerationType.IDENTITY)
    private Long pno;
    . . .
    @ManyToMany(mappedBy = "posts")
    @ToString.Exclude
    @Builder.Default
    private List<Category> categories 
                         = new ArrayList<>();
}

 

결과 DDL

위와 같은 설정 결과 생성된 테이블의 구조는 다음과 같다.

자동으로 1:N의 관계로 풀어주게 된다.

 

더보기

 

Hibernate: 
    create table category (
        cno bigint generated by default as identity,
        name varchar(255),
        primary key (cno)
    )
Hibernate: 
    create table category_posts (
        categories_cno bigint not null,
        posts_pno bigint not null
    )
Hibernate: 
    create table post (
        created timestamp(6),
        modified timestamp(6),
        pno bigint generated by default as identity,
        writer bigint,
        content varchar(255),
        title varchar(255),
        primary key (pno)
    )    
Hibernate: 
    alter table if exists category_posts 
       add constraint FKmx347vmsshx3qxcxeu9ruthrk 
       foreign key (posts_pno) 
       references post
Hibernate: 
    alter table if exists category_posts 
       add constraint FK66viirlv9o5bys7a22r4jq1r9 
       foreign key (categories_cno) 
       references category

즉 자동으로 Category_Posts이라는 연계 테이블을 생성하는데 이 테이블은 Post, Category와 각각 1:N의 관계를 맺게 된다. 연계 테이블의 P.K는 Post와 Category에 대한 F.K를 합쳐서 복합키로 사용한다.

언뜻 보면 매우 간단해보이지만 이렇게 사용할 수 있는 경우는 연계 테이블이 단순히 복합키 만 가지는 경우이다. 즉 Category_Posts에  추가일, 수정일 등 추가적으로 필요한 컬럼들이 있다면 사용할 수 없다.

따라서 M:N의 관계에서는 @ManyToMany에 의해 자동으로 생성되는 연계 테이블을 사용하기 보다연계 테이블과 엔티티를 직접 생성해서 1:N의 관계를 명시적으로 주는 것을 권장한다.

package com.doding.board.post.model.entity;

import com.doding.board.common.entity.BaseEnttiy;
import jakarta.persistence.*;

import java.time.LocalDateTime;

@Entity
public class CategoryPost extends BaseEnttiy {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long cpno;

    @ManyToOne
    @JoinColumn(name = "pno")
    private Post post;

    @ManyToOne
    @JoinColumn(name = "cno")
    private Category category;
}
@Entity
public class Category  extends BaseEnttiy{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long cno;

    @OneToMany(mappedBy = "category")
    @Builder.Default
    @ToString.Exclude
    private List<CategoryPost> categoryPosts = new ArrayList<>();
}
@Entity
public class Post extends BaseEnttiy {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long pno;

    @OneToMany(mappedBy = "post")
    @ToString.Exclude
    @Builder.Default
    private List<CategoryPost> categoryPosts = new ArrayList<>();
}

@ManyToOne은 이전 포스트에서 많이 살펴봤기 때문에 추가적인 설명은 생략한다.

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.