개발일지/JPA

다양한 연관관계 매핑

티에리앙리 2022. 7. 7. 20:50

연관관계 매핑을 다시 한 번 정리해보려고 한다.

연관관계 매핑 시 고려할 3가지

다중성 결정하기

  • 다대일
  • 일대다
  • 일대일
  • 다대다

단방향, 양방향 정하기

  • 테이블의 경우 : '방향'이라는 개념 없음, 그냥 외래키로 조인하면 두 테이블이 연관관계를 맺음
  • 객체의 경우 : 연관관계 매핑을 객체 참조하는 방식으로 하여,참조가 2군데 있어야함 ( A->B, B->A )→ 연관관계의 주인을 만들어야함
  • → 외래키를 뭐로 할지, 누가 외래키를 관리할 지 정해줘야 함

연관관계의 주인 정하기

양방향의 경우, 누가 주인( = 외래키를 관리하는 쪽)인지 정해주자

 

다대일 @ManyToOne

다대일 단방향 [N:1] : 주인인 '다'쪽에서는 '일'쪽 참조 가능하나, 반대는 조회도 불가능!

다대일 양방향 [N:1, 1:N] : 서로 참조하면 주인아닌 쪽에서는 조회 가능해짐, 연관관계 편의 메소드를 만들어주면 좋음 (무한 루프 주의!)

아래 코드는 Member에서 다대일로 단방향을 걸고 Team에서 Member로 일대다를 걸어 양방향을 완성한 코드이다.

@Entity
@NoArgsConstructor
@ToString(exclude = "team")
@Getter @Setter
public class Member {
    @Id
    @Column(name = "MEMBER_ID")
    private String id;

    // 아래코드만 있으면 단방향, 양방향은 team 쪽에서 양방향을 걸어줘야한다.
    @ManyToOne //★ N 대 1 에서 N 쪽에 외래키를 둔다.
    @JoinColumn(name = "TEAM_ID") //★ 외래키가 있는 주인 쪽에서 상대방한테 @JoinColumn을 건다
    private Team team;
}

@Entity
@NoArgsConstructor
@Getter @Setter
public class Team {
    @Id
    @Column(name = "TEAM_ID")
    private String id;

    // 아래 코드가 있으면 양방향 성립
    @OneToMany(mappedBy = "team") //★ 양방향 연관관계의 주인은 Member에 있는 team이다.
    private List<Member> members = new ArrayList<>();
}

 

일대다 @OneToMany

일대다 단방향 [1:N] : '일'이 연관관계의 주인임, @JoinColumn을 넣어주어야함!

일대다 양방향 [1:N, N:1] : 이보다는 다대일 양방향('다'쪽이 주인)을 추천한다.

만약 '일'쪽에서 외래키를 관리(주인)하겠다면 헷갈리기 때문에 비추함!

→ 다대일 관계에서 주인은 항상 '다' 쪽에 있다. 하지만 일대다는 주인이 ‘일’ 이기 때문에 기존 DB의 구조와 반대된다.

→ 연관관계 관리를 위해 추가로 UPDATE SQL 실행 해야한다.

다대일 양방향을 추천한다. '다'쪽에서 '일'쪽을 조회할 일이 없더라도, '다'를 연관관계의 주인으로 설정하자!

아래는 일대다 단방향 코드이다. 일대다 양방향은 다대일 양방향으로 작성하자

@Entity
@Getter @Setter
public class Team {
    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private String id;

    private String name;

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

@Entity
@Setter
@Getter
public class Member {
    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private String id;

    private String username;
}

 

일대일 @OneToOne

  • 주 테이블이나 대상 테이블 중에 외래 키를 선택 가능하다.
  • 일대일 양방향일때는 주인이 아닌쪽에 mappedBy 를 걸어주면 된다.
  • 주 테이블에 외래 키
    • 주 객체가 대상 객체의 참조를 가지는 것 처럼 주 테이블에 외래 키를 두고 대상 테이블을 찾음
    • 객체지향 개발자 선호
    • JPA 매핑 편리
    • 장점: 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인 가능
    • 단점: 값이 없으면 외래 키에 null 허용
  • 대상 테이블에 외래 키
    • 대상 테이블에 외래 키가 존재
    • 전통적인 데이터베이스 개발자 선호
    • 장점: 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조 유지
    • 단점: 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩됨

아래는 주테이블에 외래키 일대일 단방향, 양방향 코드이다.

@Entity
@Getter
@Setter
public class User {
    @Id @GeneratedValue
    @Column(name = "user_id")
    private String id;

    @OneToOne // 단방향을 먼저 걸어준다.
    @JoinColumn(name = "locker_id") // 주인 쪽에서 @JoinColumn을 사용한다.
    private Locker locker;   
}

@Entity
@Getter @Setter
public class Locker {
    @Id @GeneratedValue
    @Column(name = "locker_id")
    private Long id;

    // (이 부분이 없으면 단방향)
    @OneToOne(mappedBy = "locker") // 이걸 걸어주고 
    private Member member;         // 객체를 참조해주면 양방향임                           
}

아래는 대상테이블에 외래키 양방향 코드이다.(대상 테이블의 외래키는 단방향이 없다.)

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

  private String username;

  @OneToOne(mappedBy = "member")
  private Locker locker;
  ...
}

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

  private String name;

  @OneToOne
  @JoinColumn(name = "MEMBER_ID")
  private Member member;

  ...
}

 

다대다 @ManyToMany

객체의 경우

  • 객체의 경우 : 다대다 관계가 가능함
  • 테이블의 경우 : 기존 테이블 두개만으로 다대다 관계를 만들수 없다.대신에 보통은 연결 테이블을 추가하여, 일대다, 다대일로 풀어내자.@ManyToMany → @OneToMany, @ManyToOne 이렇게 풀어내어 만들자.

다대다 매핑의 한계

  • 편리해 보이지만 실무에서 사용 X
  • 연결 테이블이 단순히 연결만 하고 끝나지 않음
  • 주문 시간, 수량 같은 데이터가 들어올 수 있음

@ManyToMany 는 편리한 것 같지만, 중간 테이블에 컬럼을 추가할 수 없고, 세밀하게 쿼 리를 실행하기 어렵기 때문에 실무에서 사용하기에는 한계가 있다.

 

@ManyToMany → @OneToMany, @ManyToOne 이렇게 풀어내고 + 연결 테이블용 엔티티 추가 하자

아래는 추천하는  @ManyToMany → @OneToMany, @ManyToOne 이렇게 풀어내고 + 연결 테이블용 엔티티(MemberProduct) 추가 코드

 

참고로 아래 코드는 다대일 양방향 코드가 두 번 있다.

@Entity
public class Member {

    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

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

    // ...

    @OneToMany(mappedBy = "member")
    private List<MemberProduct> memberProducts = new ArrayList<>();
}

@Entity
public class MemberProduct {

    @Id @GeneratedValue
    private Long id;

    @ManyToOne
    @JoinColumn(name = "MEMBER_ID")
    private Member member;

    @ManyToOne
    @JoinColumn(name = "PRODUCT_ID")
    private Product product;
}

@Entity
public class Product {

    @Id @GeneratedValue
    private Long id;

    private String name;

    @OneToMany(mappedBy = "product")
    private List<MemberProduct> memberProducts = new ArrayList<>();

}