SQL mapper & ORM/JPA

6. 다양한 연관관계 매핑

wooweee 2023. 10. 4. 08:30
728x90

1. 연관관계 매핑시 고려사항 3가지

  1. 다중성
  2. 단방향, 양방향
  3. 연관관계의 주인

 

1.1. 다중성

  • 다대일 : @ManyToOne (권장)
  • 일대다 : @OneToMany (억지)
  • 일대일 : @OneToONe
  • 다대다 : @ManyToMany (사용 금지)

 

1.2. 단방향, 양방향

  • 테이블
    • 외래 키 하나로 양쪽 조인 가능
    • 사실 방향이라는 개념이 없음
  • 객체
    • 참조용 필드가 있는 쪽으로만 참조 가능
    • 한쪽만 참조하면 단방향
    • 양쪽이 서로 참조하면 양방향

 

1.3. 연관관계의 주인

  • 테이블은 외래 키 하나로 두 테이블이 연관관계를 맺음
  • 객체 양방향 관계는 A->B, B->A 처럼 참조가 2군데
  • 객체 양방향 관계는 참조가 2군데 있음. 둘중 테이블의 외래 키를 관리할 곳을 지정해야함
  • 연관관계의 주인: 외래 키를 관리하는 참조
  • 주인의 반대편: 외래 키에 영향을 주지 않음, 단순 조회만 가능

 

 

2. 다대일

  • 단방향
    • 가장 많이 사용
  • 양방향
    • FK가 있는 쪽이 연관관계 주인
    • 양쪽을 서로 참조하도록 개발

단방향 양방향

 

3. 일대다

  • 주인이 1인 경우
  • 권장하지 않는 model
package hellojpa.chap6.oneByN;

import javax.persistence.*;

@Entity
public class MemberOneByN {
    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;
    @Column(name = "USERNAME")
    private String username;

    /* 1:n 양방향 매핑
     * 아래처럼 하면 에러도 안나지만 누가 주인인지 모르기 때문에 망함. 에러도 안냄
     * 그래서 insertable = false, updatable = false 넣어서 읽기 전용으로 만들어야함.
     * */
    @ManyToOne
    // @JoinColumn(name = "TEAM_ID")//이렇게 하면 망함
    @JoinColumn(name = "TEAM_ID", insertable = false, updatable = false) // read only로 만들어야함.
    private TeamOneByN team;
}
package hellojpa.chap6.oneByN;

import javax.persistence.*;

@Entity
public class TeamOneByN {
    @Id 
    @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    private String name;
    @OneToMany
    @JoinColumn(name = "TEAM_ID")
    private List<MemberOneByN> members = new ArrayList<>();
}

 

3.1. 일대다 단방향

  • team은 member를 알고 싶고 member는 team을 알고 싶지 않을 경우
  • 객체 입장에서는 가능
  • db 입장에서는 절대 불가. 무조건 n 쪽에 fk가 존재하기 때문

 

  • 관계의 틀어짐
    • 1이 연관관계의 주인
    • 테이블은 n에 FK가 존재
  • @JoinColumn을 꼭 사용해야 함. 사용 안할 시, @JoinTable 생성됨
    -> table입장에서는 n쪽이 아니라서 fk column도 아닐텐데 mappedBy도 없으니깐 연결 table을 만들어야 말이 되기 때문에 생성함
    -> n이 아닌 것은 어떻게 아나? @OneToMany여서 해당 엔티티가 one이라는 것을 인지

 

  • 정리
    • 문제점 : 엔티티가 관리하는 fk가 다른 table에 있다. 연관관계 관리를 위해 추가로 update sql 실행 -> 운영에 아주 안좋음
    • 결론 : 일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용

 

3.2. 일대다 양방향 

  • 굉장히 억지성
  • 읽기 전용 fk 키가 생긴다.
  • 2개다 TEAM_ID로 가기 때문에 Team team의 @JoinColumn에는 insertable, updateable은 false로 두어야한다.

  • 정리
    • 공식적으로 존재하지 않는다.
    • @JoinColumn(insertable=false, updateable=false)
    • 읽기 전용 필드를 사용해서 양방향처럼 사용하는 방법
    • 정말 써야하는 경우를 제외하고는 다대일 양방향 써라

 

 

4. 일대일

  • 일대일 관계는 그 반대도 일대일
  • 주 테이블이나 대상 테이블 중에 외래 키 선택 가능
    • 주 테이블에 외래 키 (개발자 선호)
    • 대상 테이블에 외래 키 (dba 선호)
  • 외래 키에 데이터베이스 유니크(UNI) 제약조건 추가

 

package hellojpa.chap6.oneByOne;

@Entity
public class LockerOneByOne {
    @Id @GeneratedValue
    private Long id;
    private String name;
    // 양방향
    @OneToOne(mappedBy = "locker")
    private MemberOneByOne member; // 읽기 전용
}

 

package hellojpa.chap6.oneByOne;

import javax.persistence.*;

@Entity
public class MemberOneByOne {
    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;
    @Column(name = "USERNAME")
    private String username;

    @ManyToOne
    // @JoinColumn(name = "TEAM_ID")//이렇게 하면 망함
    @JoinColumn(name = "TEAM_ID", insertable = false, updatable = false) // read only로 만들어야함.
    private TeamOneByOne team;

    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private LockerOneByOne locker;
}

 

4.1. 단방향

  • 회원은 1개의 라커만 가질 수 있고 라커난 1명의 회원만 가지는 경우
  • 다대일과 단방향 매핑과 유사

 

4.2. 양방향

  • 다대일 양방향 매핑처럼 외래 키가 있는 곳인 연관관계 주인
  • 반대편은 mappedBy 적용

 

 

4.3. 일대일 정리

  • 주 테이블에 외래 키
    • 주 객체가 대상 객체의 참조를 가지는 것 처럼 주 테이블에 외래 키를 두고 대상 테이블을 찾음
    • 객체지향 개발자 선호
    • JPA 매핑 편리
    • 장점: 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인 가능
    • 단점: 값이 없으면 외래 키에 null 허용

  • 대상 테이블에 외래 키
    • 대상 테이블에 외래 키가 존재
    • 전통적인 데이터베이스 개발자 선호
    • 장점: 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조 유지
    • 단점: 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩됨(프록시는 뒤에서 설명) , 항상 양방향으로 만들어야한다.

 

 

5. 다대다

  • 실무에서 사용하지 말 것
  • table은 정규화로 인한 다대다가 불가하지만 객체는 컬렉션을 사용해서 객체 2개로 다대다 관계 가능하다.
  • @ManyToMany 사용
  • @JoinTable로 연결 테이블 지정
  • 단방향 양방향 가능
package hellojpa.chap6.nByN;

@Entity
public class MemberNByN {
    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;
    @Column(name = "USERNAME")
    private String username;

    @ManyToOne
    // @JoinColumn(name = "TEAM_ID")//이렇게 하면 망함
    @JoinColumn(name = "TEAM_ID", insertable = false, updatable = false) // read only로 만들어야함.
    private TeamNByN team;

    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private LockerNByN locker;

    @ManyToMany
    @JoinTable(name = "MEMBER_PRODUCT")
    private List<ProductNByN> products = new ArrayList<>();
}

 

package hellojpa.chap6.nByN;

@Entity
public class ProductNByN {
    @Id @GeneratedValue
    @Column(name = "PRODUCT_ID")
    private Long id;
    private String name;

    @ManyToMany(mappedBy = "products")
    private List<MemberNByN> mebers = new ArrayList<>();

}

 

5.1. 다대다 매핑의 한계

  • 연결 테이블이 단순히 연결만하고 끝나는게 아닌데 이렇게 숨어있는 table로 나타나면 사용에 불리

 

5.2. 다대다 한계 극복

  • 직접 연결 테이블용 엔티티를 추가해서 @ManyToOne으로 연결 할 것 
package hellojpa.chap6.nByN;

@Entity
public class MemberProduct {
    @Id
    @GeneratedValue
    private Long id;

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

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

    private int count;
    private int price;
    private LocalDateTime orderDateTime;
}

 

package hellojpa.chap6.nByN;

@Entity
public class MemberNByN {
    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;
    @Column(name = "USERNAME")
    private String username;

    @ManyToOne
    // @JoinColumn(name = "TEAM_ID")//이렇게 하면 망함
    @JoinColumn(name = "TEAM_ID", insertable = false, updatable = false) // read only로 만들어야함.
    private TeamNByN team;

    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private LockerNByN locker;

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

 

package hellojpa.chap6.nByN;

@Entity
public class ProductNByN {
    @Id @GeneratedValue
    @Column(name = "PRODUCT_ID")
    private Long id;
    private String name;

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

 

 

6. 어노테이션 속성

6.1. @JoinColumn

속성 설명 기본값
name 매핑할 외래 키 이름 필드명
referencedColumnName 외래 키가 참조하는 대상 테이블의 컬럼명 참조하는 테이블의 기본 키 컬럼명
foreignKey(DDL) 외래 키 제약조건을 직접 지정할 수 있다.  
unique, nullable, insertable, updatable, columnDefinition, table @Column의 속성과 동일  

 

6.2.ManyToOne

속성 설명 기본값
optional false로 설정하면 연관된 엔티티가 항상 있어야 한다. True
fetch 글로벌 패치 전략 설정 EAGER, LAZY
cascade 영속성 전이 기능을 사용  

 

6.3. OneToMany

속성 설명 기본값
mappedBy 연관관계의 주인 필드를 선택한다.  
fetch 글로벌 패치 전략 설정 EAGER, LAZY
cascade 영속성 전이 기능을 사용