Programming/Spring Boot

JPA 연관 관계 매핑 - 조인 테이블(@JoinTable) 개념과 적용 방법

Jan92 2023. 6. 21. 21:58
반응형

JPA @JoinTable 개념과 적용 방법

Join Column, Join Table

관계형 데이터베이스에서는 정규화를 통해 의미 있는 데이터의 집단으로 테이블이 구성되며, 이렇게 구성된 각 테이블끼리는 연관 관계를 갖게 되는데요.

서로 연관 관계가 있는 데이터가 여러 테이블에 나눠서 저장될 수 있기 때문에 필요한 경우 여러 테이블에서 데이터를 효과적으로 검색하기 위해 조인(join)이 사용되는 것은 필수적입니다.

 

* 정규화란 불필요한 데이터 모델의 중목을 최소화하고, 데이터의 일관성과 유연성을 확보하기 위한 목적으로 테이블을 구조화하는 프로세스입니다.

 

데이터베이스에서 테이블의 연관 관계를 설계하는 방법은 'Join Column'을 사용하는 방법과 'Join Table'을 사용하는 방법 크게 두 가지로 나뉩니다.

대부분의 경우 외래키(Foreign Key)를 가지고 직접적으로 테이블 사이의 연관 관계를 설정하는 '조인 컬럼(join column)'을 통한 방법이 사용되는데요.

다대다 관계를 일대다, 다대일로 풀어내는 등, 데이터 베이스 설계에 따라 필요한 경우에는 별도의 테이블을 만들어서 각 테이블의 외래키를 통해 연관 관계를 설정하는 '조인 테이블(join table)'을 통한 방법도 사용됩니다.

(조인 테이블을 사용하여 연관 관계를 설정하는 방법의 경우 테이블이 하나 더 생성되기 때문에 관리해야 할 테이블이 늘어나고, 데이터를 조회할 때 해당 조인 테이블도 함께 조인해야 한다는 점이 단점이 될 수 있습니다.)

 

아래 내용은 별도의 테이블로 연관 관계를 관리하는 Join Table 방식을 사용할 때 jpa 연관 관계 매핑이 어떻게 설정되는지에 대한 적용 방법입니다.

 

 


 @JoinTable 어노테이션에 사용되는 속성

name : 사용할 조인 테이블의 테이블 명을 설정합니다.

joinColumns : 현재 Entity에서 참조할 외래키(fk)를 설정합니다.

inverseJoinColumns : 반대 방향 Entity를 참조할 외래키(fk)를 설정합니다.

 

@JoinColumn 어노테이션의 referencedColumnName 속성은 외래키가 참조하는 대상 테이블의 컬럼명을 지정해 주는 역할을 하며, 기본 값(default)은 참조하는 테이블의 기본키(pk) 컬럼명이 됩니다.

 

 


1:1 연관 관계

1:1 연관 관계 @JoinTable

public class User {
    @Id
    @Column(name = "user_id")
    private Long id;

    @Column(name = "name")
    private String name;

    @OneToOne
    @JoinTable(name = "user_user_info",
    joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "user_id")},
    inverseJoinColumns = {@JoinColumn(name = "user_info_id", referencedColumnName = "user_info_id")})
    private UserInfo userInfo;
    
    ... 
    // getter, setter
}

(User Class)

 

@Entity
public class UserInfo {
    @Id
    @Column(name = "user_info_id")
    private Long id;

    @Column(name = "address")
    private String address;

    //단방향일 경우 없어도 되는 코드
    @OneToOne(mappedBy = "userInfo")
    private User user;
    
    ...
    // getter, setter
}

(UserInfo Class)

 

1:1 연관 관계에서 조인 테이블 방식을 사용하는 경우, 조인 테이블의 외래키 컬럼 각각에 대해 유니크 제약 조건을 걸어야 하는데요.

(기본키의 경우 유니크 제약 조건이 걸려있기 때문에 기본키가 아닌 외래 키에 대한 유니크 제약 조건만 설정해 주면 됩니다.)

 

UserInfo에서 @OneToOne에 mappedBy를 설정한 것은 UserInfo에서 User를 참조하는 양방향일 경우에만 필요하며, 만약 단방향이라면 해당 코드는 없어도 됩니다.

 

 


1:N 연관 관계

1:N 연관 관계 @JoinTable

@Entity
public class Board {
    @Id
    @Column(name = "board_id")
    private Long id;

    @Column(name = "title")
    private String title;

    @OneToMany
    @JoinTable(name = "board_comment",
            joinColumns = @JoinColumn(name = "board_id"),
            inverseJoinColumns = @JoinColumn(name = "comment_id"))
    private List<Comment> comments = new ArrayList<Comment>();
    
    ...
    // getter, setter
}

(Board Class)

 

@Entity
public class Comment {
    @Id
    @Column(name = "comment_id")
    private Long id;

    @Column(name = "contents")
    private String contents;
    
    ...
    // getter, setter
}

(Comment Class)

 

1:N 연관 관계에서 조인 테이블 방식을 사용하는 경우, 기존의 board 테이블과 comment 테이블이 1:N으로 직접 매핑되던 것을 조인 테이블인 board_comment에서 매핑 역할을 대신하게 되는데요.

(board_comment 테이블과 comment 테이블은 1:1로 매핑됩니다.)

 

board_comment에서는 comment_id가 기본키(pk)가 되고 board_id가 외래키(fk)가 됩니다.

 

 


N:1 연관 관계

N:1 연관 관계 @JoinTable

@Entity
public class Player {
    @Id
    @Column(name = "player_id")
    private Long id;

    @Column(name = "name")
    private String name;

    @ManyToOne(optional = false)
    @JoinTable(name = "player_grade",
            joinColumns = @JoinColumn(name = "player_id"),
            inverseJoinColumns = @JoinColumn(name = "grade_id"))
    private Grade grade;
    
    ...
    // getter, setter
}

(Player Class)

 

@Entity
public class Grade {
    @Id
    @Column(name = "grade_id")
    private Long id;

    @Column(name = "code")
    private String code;

    @OneToMany(mappedBy = "grade")
    private List<Player> players = new ArrayList<Player>();
    
    ...
    // getter, setter
}

(Grade Class)

 

1:N 연관 관계에서는 1쪽에 @OneToMany 어노테이션과 함께 @JoinTable 관련 설정을 한 반면, N:1 연관 관계에서는 N 쪽에 @ManyToOne 어노테이션과 함께 @JoinTable 관련 설정을 하게 됩니다.

 

@ManyToOne 어노테이션의 optional 속성의 경우 값이 ture일 때, 해당 객체에 null이 들어갈 수 있는데요.

여기서는 false로 설정함으로써 연관된 Entity(= Grade)가 항상 있어야 한다는 것이 보장됩니다.

(optional = false 속성으로 인해 연관된 Entity가 항상 있기 때문에 inner join을 사용할 수 있습니다.)

 

 


N:M 연관 관계

N:M 연관 관계 @JoinTable

@Entity
public class Student {
    @Id
    @Column(name = "student_id")
    private Long id;

    @Column(name = "name")
    private String name;

    @ManyToMany
    @JoinTable(name = "student_class",
            joinColumns = @JoinColumn(name = "student_id"),
            inverseJoinColumns = @JoinColumn(name = "class_id"))
    private List<Class> classes = new ArrayList<Class>();
    
    ...
    // getter, setter
}

(Student Class)

 

@Entity
public class Class {
    @Id
    @Column(name = "class_id")
    private Long id;

    @Column(name = "name")
    private String name;
    
    ...
    // getter, setter
}

(Class Class)

 

N:M 연관 관계에서 조인 테이블을 사용할 경우, 조인 테이블의 두 컬럼을 하나로 복합 유니크 제약 조건을 걸어야 합니다.

(하나의 컬럼에만 유니크 제약 조건을 걸 경우 N:M으로 매핑할 수 없습니다.)

 

 


여기까지 @JoinTable 방식을 사용하기 위한 jpa 연관 관계 매핑 설정에 대해 살펴보았는데요.

주의할 점으로는 조인 테이블에 컬럼을 추가하게 되는 경우 @JoinTable 전략을 사용하지 못한다는 것이 있습니다.

 

 

< 참고 자료 >

https://ocwokocw.tistory.com/139

https://parkhyeokjin.github.io/jpa/2019/10/28/JPA-chap6.html

반응형