Post

연관관계

연관관계

연관관계


연관관계 목차


연관관계란?

RDBMS는 정해진 스키마에 따라 데이터를 테이블 형태로 저장한다.

NoSQL은 JSON 형태의 도큐먼트 구조로 데이터를 저장한다.


RDBMS에서 가장 중요한 개념 중 하나가 테이블 간의 연관관계이다.

연관관계 매핑이란 데이터베이스 테이블 간의 관계를 객체 모델에 그대로 반영하는 것을 의미한다.



기본 용어

  • PK(Primary Key)는 테이블에서 각 레코드를 고유하게 식별하는 기본 키이다.

  • FK(Foreign Key)는 다른 테이블의 PK를 참조하는 외래 키이다.

테이블에 FK 컬럼을 두어 관계를 맺는 것을 연관관계 설정이라고 한다.



연관관계 매핑을 위해 필요한 요소

연관관계 매핑이 정상적으로 동작하기 위해서는 다음 요소들이 필요하다.

  1. 연관관계의 종류
  2. 객체 간 참조 설정
  3. 매핑 방향 설정
  4. 비즈니스 로직 반영




연관관계 매핑 종류

  • OneToOne : 일대일

  • OneToMany : 일대다

  • ManyToOne : 다대일

  • ManyToMany : 다대다

*다대다 관계는 실무에서 사용하지 않는다.

연관관계는 어떤 도메인을 기준으로 보느냐에 따라 달라진다.


ex) User 기준에서는 하나의 User가 여러 Board를 작성할 수 있으므로 일대다(OneToMany) 관계이다.

Board 기준에서는 여러 Board가 하나의 User에 의해 작성되므로 다대일(ManyToOne) 관계이다.




매핑 방향

연관관계에는 단방향 매핑과 양방향 매핑이 있다.

단방향 매핑은 한 쪽 객체만 다른 객체를 참조하는 방식이다.

양방향 매핑은 양쪽 객체가 서로를 참조하는 방식이다.


User와 Board 관계에서

board.getUser()만 가능하면 단방향이다.

user.getBoards()도 가능하면 양방향이다.




연관관계의 주인

연관관계에서는 반드시 주인을 지정해야 한다.

연관관계의 주인만이 외래 키를 관리할 수 있다.

등록과 수정은 주인만 가능하고 조회는 양쪽 모두 가능하다.


일반적으로 FK를 가지고 있는 쪽이 연관관계의 주인이 된다.

1:N 관계에서는 항상 N 쪽이 주인이 된다.




일대일 단방향 매핑

사용자는 하나의 게시글만 쓸 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Entity
@Getter
@Setter
@NoArgsConstructor
public class User {

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

    private String email;

    @OneToOne
    @JoinColumn(name = "board_id")
    private Board board;
}
1
2
3
4
5
6
7
8
9
10
11
12
@Entity
@Getter
@Setter
@NoArgsConstructor
public class Board {

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

    private String title;
}

@OneToOne은 연관관계의 주인 쪽에서 사용한다.

@JoinColumn은 외래 키 컬럼을 지정한다.


@JoinColumn을 생략하면 중간 테이블이 생성되므로 반드시 명시하는 것이 좋다.




option

fetch 옵션

fetch 옵션은 연관된 엔티티를 언제 로딩할지 결정한다.

  • EAGER는 즉시 로딩이다.

  • LAZY는 지연 로딩이다.


기본값은 다음과 같다.

  • OneToOne, ManyToOne은 EAGER

  • OneToMany, ManyToMany는 LAZY


실무에서는 성능 문제로 LAZY 사용을 권장한다.

1
@OneToOne(fetch = FetchType.LAZY)



ex) User와 Registry의 1:N 관계에서 EAGER와 LAZY 쿼리문 차이

image image




optional 옵션

optional 옵션은 null 허용 여부를 설정한다.

1
2
@OneToOne(optional = false) // null 허용 
@JoinColumn(nullable = false)




양방향 연관관계 값 설정

양방향 매핑에서는 두 객체의 값을 함께 설정해야 한다.

1
2
3
4
public void addBoard(Board board) {
    board.setUser(this);
    this.board = board;
}

한 쪽만 설정하면 객체 상태가 불완전해진다.

편의 메서드를 만들어 두 객체를 동시에 설정하는 것이 좋다.




순환 참조 문제

양방향 매핑에서 @ToString을 사용하면 순환 참조가 발생할 수 있다.


User의 toString이 Board를 호출하고

Board의 toString이 다시 User를 호출하게 된다.


이로 인해 StackOverflowError가 발생한다.

1
2
@ToString.Exclude
@JsonIgnore

위 어노테이션으로 참조 필드를 제외해야 한다.




DTO 사용 이유

엔티티를 그대로 반환하면 순환 참조 위험이 있다.

불필요한 필드까지 외부에 노출된다.


DTO를 사용하면 필요한 데이터만 전달할 수 있다.

순환 참조 문제를 자연스럽게 해결할 수 있다.

코드 가독성도 좋아진다.




다대일 양방향 매핑

한명의 사용자가 여러 게시글을 작성할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Entity
@Getter
@Setter
@NoArgsConstructor
public class Board {

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

    private String title;

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;

    public void addUser(User user) {
        user.getBoard().add(this);
        this.user = user;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Entity
@Getter
@Setter
@NoArgsConstructor
public class User {

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

    private String name;

    @OneToMany(mappedBy = "user")
    private List<Board> board = new ArrayList<>();
}

mappedBy에는 연관관계 주인이 가진 필드명을 작성한다.

mappedBy를 작성하지 않으면 단방향 매핑 두 개와 동일하다.


일대다 관계에서 컬렉션 필드는 반드시 초기화해야 한다.

초기화하지 않으면 NullPointerException이 발생한다.




컬렉션 타입 정리

  • List는 순서가 있고 중복을 허용한다.
  • Set은 순서가 없고 중복을 허용하지 않는다.
  • Map은 Key-Value 구조이며 Key 중복을 허용하지 않는다.


인프런 관련 글



reference
연관관계 매핑 기초
[JPA] JPA가 지원하는 컬렉션

This post is licensed under CC BY 4.0 by the author.