엔티티의 연관관계를 매핑할 때는 3가지를 고려해야한다.

  1. 다중성 : 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M)
  2. 단방향, 양방향 :  (객체 참조)
  3. 연관관계의 주인 : 양방향일 때, 연관 관계에서 관리 주체

연관관계 매핑 : 비즈니스 로직, 비즈니스 요구사항에 따라 개발자가 더 적절한 관계 설정 방법을 선택

 


다중성

다대일(@ManyToOne)

일대다(@OneToMany)

일대일(@OneToOne)

다대다(@ManyToMany)

 

※ 보통 다대일과 일대다 관계를 많이 사용하고 다대다 관계는 실무에서 거의 사용하지 않는다.

 

단방향, 양방향

  • 데이터베이스 테이블은 외래 키 하나로 양 쪽 테이블 조인이 가능하다.
  • 데이터베이스는 양방향으로 쿼리가 가능하기 때문에 단방향이니 양방향이니 나눌 필요가 없다.
  • 반면, 객체는 참조용 필드가 있는 객체만 다른 객체를 참조하는 것이 가능하다.
  • 그렇기 때문에 두 객체 사이에 하나의 객체만 참조용 필드를 갖고 참조하면 단방향 관계, 두 객체 모두가 각각 참조용 필드를 갖고 참조하면 양방향 관계라고 한다.
  • 사실, 엄밀하게는 양방향 관계↔️는 없고 두 객체가 단방향 참조를 각각 가져서 양방향 관계처럼 사용하고 말하는 것

 

연관관계 주인

  • 두 객체(A, B)가 양방향 관계, 다시 말해 단방향 관계 2개(A→B, B→A)를 맺을 때, 양방향 참조가 존재하기 때문에 어느 쪽에서 외래키를 관리할지 정해야한다.
  • 외래 키를 가진 테이블을 매핑한 엔티티에서 외래 키를 관리하는게 효율적이다.
  • 따라서 이곳을 연관관계의 주인으로 선택한다. 외래 키를 가진 엔티티가 주인이라고 생각하면 쉽다. 
  • 일대다, 다대일 관계에서 항상 '다'쪽이 외래키를 가진다. 주인이 아닌 쪽은 외래 키를 변경할 수 없고 읽기만 가능하다.  

 


다대일(N : 1) 단방향

게시판(Board)과 게시글(Post)의 관계로 이해해 보기! 

  • 요구 사항
    • 하나의 게시판(1)에는 여러 게시글(N)을 작성할 수 있습니다.
    • 하나의 게시글은 하나의 게시판에만 작성할 수 있다.
    • 게시글과 게시판은 다대일 관계를 갖습니다.

데이터베이스를 기준으로 다중성(게시글N : 게시판1)을 결정했다. 즉, 외래 키를 게시글(N)이 관리하는 일반적인 형태.

다대일 단방향에서는 다 쪽인 Post에서 @ManyToOne 만 추가해준 것을 확인할 수 있다. 반대로 Board에서는 참조하지 않는다. (단방향이기 때문)

@Entity
public class Post {
    @Id @GeneratedValue
    @Column(name = "POST_ID")
    private Long id;

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

    @ManyToOne
    @JoinColumn(name = "BOARD_ID")
    private Board board;
    //... getter, setter
}

@Entity
public class Board {
    @Id @GeneratedValue
    private Long id;
    private String title;
    //... getter, setter

 

다대일(N : 1) 양방향

다대일 양방향으로 만드려면 일(1) 쪽에 @OneToMany 를 추가하고 양방향 매핑을 사용했으니 연관 관계의 주인을 mappedBy 로 지정해준다. mappedBy로 지정할 때 값은 대상이 되는 변수명을 따라 지정하면 된다. 여기서는 Post 객체(대상)의 board라는 이름의 변수이기 때문에 board로 지정

@Entity
public class Post {
    @Id @GeneratedValue
    @Column(name = "POST_ID")
    private Long id;

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

    @ManyToOne
    @JoinColumn(name = "BOARD_ID")
    private Board board;
    //... getter, setter
}

@Entity
public class Board {
    @Id @GeneratedValue
    private Long id;
    private String title;

    @OneToMany(mappedBy = "board")
    List<Post> posts = new ArrayList<>();
    //... getter, setter
}

 

일대다(1 : N) 단방향  

(참고로 실무에서는 일대다(1:N) 단방향은 거의 쓰지 않는다.)

데이터베이스 입장에서는 무조건 다(N)쪽에서 외래키를 관리한다. 일(1)쪽 객체에서 다(N) 쪽 객체를 조작(생성,수정,삭제)하는 방법!

@Entity
public class Post {
    @Id @GeneratedValue
    @Column(name = "POST_ID")
    private Long id;

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

@Entity
public class Board {
    @Id @GeneratedValue
    private Long id;
    private String title;

    @OneToMany
    @JoinColumn(name = "POST_ID") //일대다 단방향을 @JoinColumn필수
    List<Post> posts = new ArrayList<>();
    //... getter, setter
}

@OneToMany에 mappedBy가 없어진다. 양방향이 아니기 때문. 대신 @JoinColumn을 이용해서 조인을 한다.

실제 사용은 아래와 같다.

//...
Post post = new Post();
post.setTitle("가입인사");

entityManager.persist(post); // post 저장

Board board = new Board();
board.setTitle("자유게시판");
board.getPosts().add(post);

entityManager.persist(board); // board 저장
//...

위와 같은 시나리오로 동작을 살펴보면, post를 저장할 때는 멀쩡하게 insert 쿼리가 나가지만 그 다음이 문제이다.

board를 저장할 때는 Board를 insert하는 쿼리가 나간 후에 post를 update하는 쿼리가 나간다.

왜냐하면 board.getPosts().add(post); 부분 때문!

Board 엔티티는 Board 테이블에 매핑되기 때문에 Board 테이블에 직접 지정할 수 있으나, Post 테이블의 FK(BOARD_ID)를 저장할 방법이 없기 때문에 조인 및 업데이트 쿼리를 날려야 하는 문제가 있다.

 

치명적인 단점

  • 일만 수정한 것 같은데 다른 수정이 생겨 쿼리가 발생하는 것.
    • Board를 저장했는데 왜 Post가 수정이 되지? 이런 생각을 하게 만듦.
    • 업데이트 쿼리 때문에 성능상 이슈는 그렇게 크지는 않음.

 

정리 : 일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하자. 엔티티를 매핑한 테이블이 아닌 다른 테이블의 외래 키를 관리한다는 것은 성능 문제도 있지만 관리도 부담스럽다. 해결법은 다대일 양방향 매핑을 사용하는 것이다. 

 

일대다(1 : N) 양방향

(실무 사용 금지 )

일대다 양방향 매핑은 존재하지 않는다. 대신 다대일 양방향 매핑을 사용해야한다. (다대일 양방향과 일대다 양방향은 사실 똑같은 말이다) 양방향 관계에서 @OneToMany는  주인이 될 수 없다. 관계형 데이터베이스 특성상 일대다, 다대일 관계는 항상 '다'쪽에 외래 키가 있다. 따라서 연관관계의 주인은 항상 @ManyToOne이다. 

 

그렇다고 일대다 양방향 매핑이 완전히 불가능하지는 않다. 일대다 단방향 매핑 반대편에 같은 외래 키를 사용하는 다대일 단방행 매핑을 읽기 전용으로 추가하면 된다.

@Entity
public class Member {

    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;
    
    private String username;
    
    @ManyToOne
    @JoinColumn(name = "TEAM_ID", insertable = false, updatable = false)
    private Team team;
    

    ...
 
}

@Entity
public class Team {
    @Id
    @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    
    @OneToMany
    @JoinColumn(name = "TEAM_ID") //MEMBER테이블의 TEAM_ID(FK)
    private List<Member> members = new ArrayList<Member>();
    
    private String name;
    
    ...
    
}

둘 다 같은 키를 관리하므로 문제가 될 수 있지만 Member의 JoinColumn속성을 보면 읽기만 가능하게 했다. 이 방법은 다대일 양방향처럼 보이게하지만 일대다 단방향 매핑이 가지는 단점을 그대로 가진다. 웬만하면 다대일 양방향 매핑을 사용하자.

 


[ 참고 자료 ]

 

 

https://jgrammer.tistory.com/entry/JPA-%EB%8B%A4%EC%96%91%ED%95%9C-%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84-%EB%A7%A4%ED%95%91-1-%EB%8B%A4%EB%8C%80%EC%9D%BC-%EC%9D%BC%EB%8C%80%EB%8B%A4-1

 

[JPA] 다양한 연관관계 매핑 (1) 다대일, 일대다

엔티티의 연관관계를 매핑할 때는 3가지를 고려해야한다. 다중성 단방향, 양방향 연관관계의 주인 먼저 두 엔티티가 일대일 관계일지 일대다 관계인지 다중성을 고려한다. 다음으로, 두 엔티티

jgrammer.tistory.com

 

https://jeong-pro.tistory.com/231

 

JPA 연관 관계 한방에 정리 (단방향/양방향, 연관 관계의 주인, 일대일, 다대일, 일대다, 다대다)

JPA에서 가장 중요한 것 JPA에서 가장 중요한 것을 뽑자면, "객체와 관계형 데이터베이스 테이블이 어떻게 매핑되는지를 이해하는 것"이라고 생각합니다. 🏅 왜냐하면 JPA의 목적인 "객체 지향 프

jeong-pro.tistory.com

 

'Coding > Spring' 카테고리의 다른 글

[27] Spring AOP  (0) 2022.12.14
[26] OAuth / 소셜 로그인  (0) 2022.12.14
[24] ERD(Entity Relationship Diagram)  (0) 2022.12.13
[23] Spring Security  (0) 2022.12.13
[22] 항해99 주특기 숙련주차 시험 Spring  (0) 2022.12.08

+ Recent posts