관리 메뉴

민우의 코딩노트

[JPA] 연관관계 매핑 - @OneToMany @ManyToOne 본문

Knowledge/Spring

[JPA] 연관관계 매핑 - @OneToMany @ManyToOne

미미누 2022. 2. 22. 02:13

https://jyami.tistory.com/21

https://dublin-java.tistory.com/51

https://ict-nroo.tistory.com/122

 

[JPA] 양방향 연관관계

양방향 연관관계와 연관관계의 주인 Team을 통해서도 getMemberList()로 특정 팀에 속한 멤버 리스트를 가져오고 싶다. 객체 설계는 위와 같이 Member에서는 Team을 가지고 있고, Team에서는 Members를 가지

ict-nroo.tistory.com

 

JPA - One To Many 단방향의 문제점

주변에서 One To Many 단방향에 관해서 물어볼 때마다 저는 항상 이렇게 대답했습니다. 김영한 님의 인프런 강의에서 봤는데~ One To Many 단방향은 좋지 않다. 차라리 양방향을 해라. 이유는 ~ 때문이

dublin-java.tistory.com

 

[JPA] 다양한 연관관계 매핑 - @OneToMany @ManyToOne @OneToOne @ManyToOne

인프런에서 에서 김영한님의 자바 ORM 표준 JPA 프로그래밍 - 기본편을 듣고 쓴 정리 글입니다. https://www.inflearn.com/course/ORM-JPA-Basic 자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 JPA를 처음 접하..

jyami.tistory.com

위 글을 보고 연관관계 매핑에 대해 정리해보았습니다.


 

  • 다대일 [N:1] : @ManyToOne
  • 일대다 [1:N] : @OneToMany
  • 일대일 [1:1] : @OneToOne
  • 다대다 [N:M] : @ManyToMany

 

양방향 연관관계

테이블의 연관관계에서 Member(*), Team(1)의 관계를 가질때, Team을 통해서 멤버 리스트를 가져오고 싶으면 Join을 통해 멤버 리스트를 가져올 수 있다. (Member의 FK 사용)

하지만 객체 연관관계에서는 Member에서도 Team 참조, Team에서도 Member를 참조하도록 설계하면 된다.

 

객체와 테이블간에 연관관계를 맺는 차이

테이블(DB)의 연관관계는 방향성이 없지만, 객체의 연관관계는 방향성이 있다.

DB는 FK 선언 하나로 양방향 관계를 맺을 수 있지만, 객체는 둘다 참조해야 한다.

 

연관관계의 주인

  • 테이블은 외래 키 하나로 두 테이블이 연관관계를 맺는다.
  • 객체 양방향 관계는 A->B, B-> A
  • 객체 양방향 관계는 참조가 2군데 있어, 둘중 테이블의 외래 키를 관리하는 곳을 지정
  • 연관관계의 주인 : 외래 키를 참조하는 곳에서 관리한다.
  • 주인의 반대편 : 외래 키에 영향을 주지 않음

다대일 단방향

@Entity
public class Member {
  @Id
  @GeneratedValue
  private Long id;
  
  @Column(name = "USERNAME")
  private String name;
  
  private int age;
​
  @ManyToOne
  @JoinColumn(name = "TEAM_ID")
  private Team team;
  ...
}

이런식으로 다대일 참조가 가능합니다.

@JoinColumn(name = "TEAM_ID")를 통해서

참조하는 컬럼의 이름을 정하고, 연관관계의 주인임을 명시합니다.

 

외래키가 있는 곳에 참조를 걸고 연관관계 매핑을 합니다.

외래키가 있는 곳이 연관관계의 주인이다.


다대일 양방향

하나의 Team에서 여러 명의 member를 가질 수 있습니다.

따라서 member 와 Team은 N:1 관계임을 알 수 있습니다. 

연관관계의 주인(member)가 FK를 관리하게 됩니다.

 

반대쪽 (team)은 읽기만 List 형태로 관리되어 읽기만 가능합니다.

이때 mappedBy로  team과 연관이 있는 것을 알려줍니다.

컬렉션을 매핑하는데, 관례로 ArrayList로 초기화합니다. 

NPE(Null Point Exception)을 방지하기 위해서 입니다.

 

외래키가 있는 쪽(Team)이 연관관계의 주인입니다.

@Entity
public class Member {
  @Id
  @GeneratedValue
  private Long id;
  
  @Column(name = "USERNAME")
  private String name;
  
  private int age;
​
  @ManyToOne
  @JoinColumn(name = "TEAM_ID")
  private Team team;
  ...
}
@Entity
public class Team {
  @Id
  @GeneratedValue
  private Long id;
  
  private String name;
  
  @OneToMany(mappedBy = "team")
  private List<Member> members = new ArrayList<Member>();
  ...
}

 일대다 [1:N]

일대다 단방향

 

권장하지 않습니다. 

반대로 Team에서 외래키를 관리합니다. (team이 연관관계의 주인)

 

@JoinColumn을 사용해서 team쪽에 연관관계 주인임을 표시합니다.

Team은 Member를 알고싶은데 Member는 Team을 알고싶지 않을때입니다.

 

테이블의 일대다 관계는 항상 다쪽에 외래 키가 있는데

@JoinColumn을 사용해서 1쪽에 연관관계 주인임을 표시하지 않으면  중간 테이블을 생성하는 조인 테이블 방식을 사용합니다.

 

테이블의 연관관게에서 항상 member(다)에서 FK가 있는데,

Team(1)이 연관관계의 주인이 되어 반대편 테이블(member)의 FK인 team_id를 관리하게 되는 것입니다.

@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@Entity
public class MemberOtM {
    @Id
    @GeneratedValue
    private Long id;

    private String name;
    
}
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@Entity
public class TeamOtM {

    @Id @GeneratedValue
    private Long id;

    private String name;

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

}
 // 멤버 생성
            MemberOtM member = new MemberOtM();
            member.setName("member1");
            em.persist(member);
            
            //팀 생성
            TeamOtM team = new TeamOtM();
            team.setName("team1");
            //팀에 멤버 추가
            team.getMembers().add(member);

            em.persist(team);

멤버1를 생성하고, 팀을 생성 후, 팀의 멤버에 멤버1를 넣어줬습니다.

쿼리는 멤버, 팀 insert 각각 1번, 멤버 update 1번이 나오게 됩니다.

 

이러한 이유는 객체의 연관관계에서는 team이 member를 참조하지만

테이블의 연관관계에서는 fk를 가지고 있는 쪽이 member이기 때문에 발생합니다.

 

이러한 이유로, 테이블의 연관관계상 member(다) 쪽에서 team(일) 단방향으로 설계하고,

team에서 member의 참조가 필요하다면 @OneToMany(mappedBy = "team")을 추가해주면 좋습니다.

 

일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하는 것이 좋다고 나와있습니다.


일대다 양방향

 

// Member 클래스
    @ManyToOne
    @JoinColumn(name="TEAM_ID", insertable = false, updatable = false) //중요!!
    private Team team;

이러면 Team, Member 모두 @JoinColumn이 붙어서 둘다 연관관계의 주인이 됩니다.

그래서 JoinColumn의 옵션을 사용해서 mapping은 되어있고 값은 다 쓰는데

insertable, updatable을 막아 read 전용으로 만듭니다.

 

관리는 Team으로하고 Member는 읽기만 합니다.

 

[요약]

  • 이런 매핑은 공식적으로 존재 X
  • @JoinColumn(insertable=false, updatable=false)
  • 읽기 전용 필드를 사용해서 양방향 처럼 사용하는 방법
  • 다대일 양방향을 사용하는 것이 좋습니다.