오봉이와 함께하는 개발 블로그

JPA - 연관관계 매핑 N:1, 1:N 본문

BE/JPA

JPA - 연관관계 매핑 N:1, 1:N

오봉봉이 2022. 6. 26. 23:47
728x90

연관관계 매핑할 때 고려해야 할 세 가지

  • 다중성
    • JPA에서 나오는 어노테이션은 DB랑 매핑하기 위해 있는 것이기 때문에 DB관점에서 다중성을 기준으로 고민하자.
    • 다대일 : @ManyToOne
      • 실무에서 가장 많이 사용
    • 일대다 : @OneToMany
      • 실무에서 필요할 때 사용
    • 일대일 : @OneToOne
      • 실무에서 가끔 사용
    • 다대다 : @ManyToMany
      • 실무에서 사용을 기피해야 함.
  • 단방향, 양방향
    • 테이블
      • 외래키 하나로 양쪽 조인 가능하기 때문에 사실상 방향이라는 개념이 없다.
    • 객체
      • 참조용 필드가 있는 쪽으로만 참조 가능하기 때문에 한쪽만 참조하면 단방향, 양쪽이 서로 참조하면 양방향이라 한다.
        • 사실 양방향이라는 것도 없다고 해야한다.
          • 객체 입장에서는 서로 참조를 하면 단방향이 두 개가 있는 것임
          • 용어상 쉽게 이해할 수 있게 하기 위해 양방향이라는 개념이 탄생됨
  • 연관관계 주인(양방향일 때)
    • 연관관계 주인 : 외래키를 관리하는 참조
    • 주인의 반대편 : 외래키에 영향을 주지 않고 단순 조회만 가능.
    • 테이블은 외래키 하나로 두 테이블이 연관관계를 맺는다.
    • 객체의 양방향 관계는 A to B, B to A 처럼 참조가 두 군데다.
    • 객체 양방향 관계는 참조가 두 군데 있기 때문에 테이블의 외래키를 관리할 곳을 지정해야한다.
      • 두 객체가 서로를 참조하기 때문에 어떤 객체가 값을 바꿨을 때 테이블을 업데이트할지 정해야함.

N:1 [다대일] 단방향

가장 많이 사용하는 연관관계로 다대일의 반대는 일대다가 된다.


DB 입장에서 생각하면 TEAM이 1이고 MEMBER가 N의 입장이 된다.
그럼, DB 설계에서는 당연히 N 쪽에 외래키가 가야 한다.
테이블의 MEMBER 입장에서 TEAM_ID는 TEAM 테이블을 참조하기 위함이고, 객체도 마찬가지도 team 객체를 통해 Team을 참조할 수 있다.
Member에서 Team으로만 참조할 경우 위와 같이 세팅하면 된다.
즉, 외래키가 있는 쪽으로 연관관계 참조를 걸어 매핑을 하면 된다.

@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;

정리

  • 가장 많이 사용하는 연관관계
  • 다대일의 반대는 일대다

N:1 [다대일] 양방향

외래키가 있는 쪽이 연관관계의 주인이며 양쪽이 서로를 참조하도록 개발한다.


Team 클래스에서 Member 클래스를 참조할 수 있도록 객체를 만들어주면 된다.
여기서 중요한 점은 외래키를 가진 테이블이 모든 관리를 하기 때문에 참조를 위한 객체를 만들어줘도 읽기만 가능해서 DB 테이블에 전혀 영향을 미치지 않는다는 점이다.
객체 추가는 정말 객체를 조회할 수 있도록 추가만 한 것이다.

@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;

// ============== //

@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();

정리

  • 외래키가 있는 쪽이 연관관계 주인
  • 양쪽을 서로 참조하도록 개발



1:N [일대다] 단방향


실무에서는 거의 사용하지 않지만 필요할 때 사용한다.
Team 클래스 중심으로 사용하는 구조
객체 입장에서는 이런 구조가 나올 수는 있지만, DB 입장에서는 무조건 N에 외래키가 들어가야 한다.
일대다 구조는 Team의 List members의 값을 바꿨을 때 다른 테이블(MEMBER)의 외래키를 업데이트 한다.

@OneToMany
@JoinColumn(name = "TEAM_ID")
private List<Member> members = new ArrayList<>();
Member member = new Member();
member.setName("member1");

em.persist(member);

Team team = new Team();
team.setName("team1");

team.getMembers().add(member);

em.persist(team);

위 코드를 실행하면 update문이 한 번 더 나가게 된다.
성능상 큰 문제가 없지만 효율이 떨어지는 건 분명하고 더 큰 문제는 Team 클래스에 대한 수정을 진행했는데, MEMBER 테이블에 쿼리문이 나가는데 왜 그러는지 찾지 못 하는 문제가 생길 수 있다.
또, 실제 서비스에서는 수 많은 테이블들이 엮여 있는 상황에 위와 같은 문제가 발생하면 운영이 어려워진다.

그래서 권장하는 방법은 1:N이 아닌, N:1 양방향을 사용하자

정리

  • 1:N 단방향은 1:N에서 1이 연관관계의 주인이다.
  • 테이블 1:N 관계는 항상 N 쪽에 외래키가 있다.
  • 객체와 테이블의 차이 때문에 반대편 테이블의 외래키를 관리하는 특이한 구조.
  • @JoinColumn을 꼭 사용해야 한다.
    • 그렇지 않으면 조인 테이블 방식을 사용(중간에 테이블을 하나 더 추가한다.)
  • 1:N 단방향 매핑의 단점
    • 엔티티가 관리하는 외래키가 다른 테이블에 있음
    • 연관관계 관리를 위해 UPDATE SQL이 추가로 실행됨
  • 1:N 단방향말고 차라리 N:1 양방향을 사용하자

1:N [일대다] 양방향

JPA 스팩상 존재하지는 않지만 편법으로 할 수는 있는 구조다.

@OneToMany
@JoinColumn(name = "TEAM_ID")
private List<Member> members = new ArrayList<>();

// ============== //

@ManyToOne
@JoinColumn(name = "TEAM_ID", insertable=false, updatable=false)
// insert와 update를 못 하게 막음
private Team team;

Member 클래스의 team 객체도 주인처럼 보일 수 있기 때문에 insertable, updatable에 false를 줘서 읽기 전용 매핑을 했다.

읽기만 하기 때문에 양방향 매핑을 하는 효과를 준다.

정리

  • 이런 매핑은 JPA 공식적으로 존재하지 않는다.
  • 읽기 전용 필드 @JoinColumn(insertable=false, updatable=false)를 사용해서 양방향 처럼 사용
  • N:1 양방향 을 사용하자
출처 : 인프런 김영한 지식공유자님의 스프링 부트와 JPA 실무 완전 정복 로드맵 강의
728x90
Comments