JPA기초 ManyToMany

ManyToMany

관계형 데이터베이스를 이용하다보면 ManyToMany 관계를 종종 만들어야 할 때가 있다.

시나리오.

  • 사용자 정보를 담고 있는 User 가 있다.
  • 시스템의 다양한 사용정보를 정의한 Role 이 있다.
  • 사용자는 다양한 Role 을 가질 수 있다.
  • Role 은 여러 사용자에 할당 될 수 있다.

이러한 시나리오가 전형적인 M:N 관계이다. 이러한 관계는 연관 테이블을 생성하여 비즈니스 니즈를 해결하는 것이 일반적이다.

M_n_Relation

위 다이어그램에서 보는 바와 같이 user_id, role_id 를 각각 가진 user_role 이라는 테이블으 생성 되었고.

user : user_role = 1 : N 관계가 형성이 된다.

role : user_role = 1 : N 관계가 형상이 된다.

이렇게 중간 테이블을 만들어서 연관을 맺어주면 쉽게 M:N 관계를 설정할 수 있다.

Role 엔터티 생성하기.

이제 Role 엔터티를 생성해보자. Role 은 제목만 가지고 있고, Role 에 대한 상세 권한은 이번 내용에 포함시키지 않았다.

단지 사용자가 어떠한 Role 을 가질지만으로 M:N 관계를 설저해볼 것이다.

User.java 엔터티를 생성하고 다음과 같이 작성한다.

package com.schooldevops.practical.simpleboard.entity;

import lombok.*;

import javax.persistence.*;
import java.util.Set;

@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Setter
@Entity
@Table(name = "role")
public class Role {

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

    @Column(nullable = false)
    private String name;

}

위 내용은 단순히 id(자동생성) 과 role 이름만 있는 단순한 엔터티를 만들었다.

연관 맺어주기.

이제부터 User 와 Role 에 대해서 연관을 맺어줄 것이다.

Role.java 부터 연관을 작성해보자.

... 중간생략 ...
    @ManyToMany
    @JoinTable(
            name="user_role",
            joinColumns = @JoinColumn(name = "role_id"),
            inverseJoinColumns = @JoinColumn(name = "user_id")
    )
    private Set<User> users;

위 내용과 같이 연관을 생성했다.

  • 하나의 Role 은 여러 User 를 가질 수 있다.
  • @ManyToMany: M:N 관계를 위한 어노테이션을 지정했다.
  • @JoinTable: 조금전에 이야기한 연관 테이블 속성을 정의한다.
    • name=”user_role”: user_role 라는 테이블을 생성하라고 지시한다.
    • joinColumns: 이것은 Role 입장에서 자신의 키가 user_role 테이블에 어떻게 매핑될지 설정한다.
      • @JoinColumn(name = “role_id”): user_role 테이블에 role_id 라는 필드로 매핑됨을 미한다.
    • inverseJoinColumns: 이것은 반대편 엔터티를 의미하며 Role 입장에서는 User 가 된다.
      • @JoinColumn(name = “user_id”): user_role 테이블에 user 테이블의 id 가 매핑되며 필드 이름은 user_id 가 된다.

위와 같이 연관을 매핑해 보았다.

User.java 테이블 연관 맺어주기.


... 중간 생략 ...
    @ManyToMany
    @JoinTable(
            name = "user_role",
            joinColumns = @JoinColumn(name = "user_id"),
            inverseJoinColumns = @JoinColumn(name = "role_id")
    )
    private Set<Role> roles;

    public void addRole(Role role) {
        if (roles == null) {
            roles = new HashSet<>();
        }

        roles.add(role);
    }
... 중간 생략 ...

Role에 매핑을 걸어보았으니, 이 부분은 그리 어렵지 않다.

  • @ManyToMany: 역시 동일하게 M:N 관계를 선언하는 어노테이션이다.
  • @JoinTable: 연관 테이블을 정의해준다.
    • name = “user_role”: user_role 테이블을 생성하도록 지시한다.
    • joinColumns: User 입장에서 자신의 키가 연관테이블의 어떠한 필드와 매핑되는지 알려준다.
      • @JoinColumn(name = “user_id”): user_id 이름으로 매핑되도록 설정했다.
    • inverseJoinColumns: User의 반대편 엔터티이므로 Role 를 의미한다.
      • @JoinColumn(name = “role_id”): 연관 테이블에 role_id 필드가 role 키와 매핑된다.

그리고 User를 기준으로 Role 을 매핑해 줄 것이므로, addRole 처럼 편의 메소드를 만들어 주었다.

생성된 결과 확인하기.

이제 위 연관을 이용하여 생성된 테이블 쿼리를 확인해 볼 차례이다.

Hibernate: 
    
    create table role (
       id bigint not null auto_increment,
        name varchar(255) not null,
        primary key (id)
    ) engine=InnoDB

role 테이블이 생성 되었다. 기본키는 id 이다.

Hibernate: 
    
    create table user (
       id varchar(255) not null,
        birth varchar(255),
        created_at datetime(6),
        name varchar(255),
        primary key (id)
    ) engine=InnoDB

user 테이블도 생성이 되었으며 역시 기본키는 id 이다.

Hibernate: 
    
    alter table user_role 
       add constraint FKa68196081fvovjhkek5m97n3y 
       foreign key (role_id) 
       references role (id)

user_role 테이블이 생성 되었다. 이것은 우리가 의도한 테이블이다.

그리고 다음과 같이 양방향 Foreign Key 가 생성되었다.

Hibernate: 
    
    alter table user_role 
       add constraint FKa68196081fvovjhkek5m97n3y 
       foreign key (role_id) 
       references role (id)
Hibernate: 
    
    alter table user_role 
       add constraint FK859n2jvi8ivhui0rl0esws6o 
       foreign key (user_id) 
       references user (id)

보는 바와 같이 user_role 테이블에 role_id 는 role 테이블의 id 기본키와 foreign key 관계를 가진다.

역시 user_role 테이블에 user_id 는 user 테이블의 id 기본키와 foreign key 관계를 가지도록 생성 되었다.

처음 우리가 지정한 방식대로 ManyToMany 가 생성된 것을 확인할 수 있다.