다양한 연관관계 매핑


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


⒈ 다중성

• 다대일 : @ManyToOne

• 일대다 : @OneToMany

• 일대일 : @OneToOne

• 다대다 : @ManyToMany


⒉ 단방향, 양방향

▷ 테이블

• 외래 키 하나로 양쪽 조인 가능

• 사실 방향이라는 개념이 없음


▷ 객체

• 참조용 필드가 있는 쪽으로만 참조 가능

• 한쪽만 참조하면 단 방향

• 양쪽이 서로 참조하면 양방향


⒊ 연관관계 주인

• 테이블은 외래 키 하나로 두 테이블이 연관관계를 맺음

• 객체 양방향 관계는 A → B, B → A 처럼 참조가 2군데

• 2개의 참조 중 테이블의 외래 키를 관리할 곳을 지정해야 함.

• 연관관계의 주인 : 외래 키를 관리하는 참조

• 주인의 반대편 : 외래 키에 영향을 주지 않음 == Read Only


다대일 [N:1]

N 쪽이 연관관계의 주인


다대일 단방향

• 가장 많이 사용하는 연관관계

• 일대다의 반대


다대일 양방향

외래 키가 있는 쪽이 연관관계의 주인

• 양쪽을 서로 참조하도록 개발

• 다대일의 양방향 관계는 객체에서 필드만 추가하면 된다.

• 양방향 관계는 DB테이블에는 아무런 영향을 주지 않는다.


예제 코드

@Entity
public class Member {

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

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
}
@Entity
public class Team {
    
    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;

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


일대다 [1:N]

다대일 양방향 매핑을 사용하자!!!


일대다 단방향

권장하지 않는 모델

• Update 쿼리가 한 번더 나간다.

• 일대다 단방향은 일대다(1:N)에서 1이 연관관계의 주인

• DB 테이블에서 일대다 관계는 항상 다(N) 쪽에 외래 키가 있음.

@JoinColumn을 꼭 사용해야 한다.

@JoinColumn을 사용하지 않으면 중간에 테이블이 하나 추가됨.

• 객체와 테이블의 차이 때문에 반대편 테이블의 외래 키를 관리하는 특이한 구조


@Entity
public class Member {

    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;
}
public class Team {
    
    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;

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


일대다 양방향

• 이런 매핑은 공식적으로 존재하지 않는다.

@JoinColumn(insertable = false, updatable = false);

• 읽기 전용 필드를 사용해서 양방향 처럼 사용하는 방법

다대일 양방향 매핑을 사용하자!!!

@Entity
public class Member {

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

    @ManyToOne
    @JoinColumn(name = "TEAM_ID", insertable = false, updatable = false);
    private Team team;
}
public class Team {
    
    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;

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


일대일 [1:1]


일대일 매핑이란?

일대일 관계는 양쪽이 서로 하나의 관계만 가진다. 예를 들어 회원은 하나의 사물함만

사용하고 사물함도 하나의 회원에 의해서만 사용된다. 일대일 관계는 다음과 같은

특징이 있다.


• 일대일 관계는 그 반대도 일대일

• 주 테이블이나 대상 테이블 중에 외래 키를 선택 가능

• 외래 키에 데이터베이스 유니크(UNI) 조건 추가


테이블 관계에서 일대다, 다대일은 항상 다(N)쪽이 외래 키를 가진다.

반면에 일대일 관계는 주 테이블이나 대상 테이블 둘 중 어느 곳이나 외래 키를 가질

수 있다. 테이블은 주 테이블이든 대상 테이블이든 외래 키 하나만 있으면 양쪽으로

조회할 수 있다. 그리고 일대일 관계는 그 반대쪽도 일대일 관계다.

따라서 일대일 관계는 주 테이블이나 대상 테이블 중에 누가 외래 키를 가질지

선택해야 한다.


주 테이블의 외래 키

다대일 연관관계 매핑처럼 외래 키가 있는 곳이 연관관계 주인.

주 객체가 대상 객체를 참조하는 것처럼 주 테이블에 외래 키를 두고 대상 테이블을

참조한다. 외래 키를 객체 참조와 비슷하게 사용할 수 있어서 객체지향 개발자들이

선호한다. 이 방법의 장점은 주 테이블이 외래 키를 가지고 있으므로, 주 테이블만

확인해도 대상 테이블과 연관관계가 있는지 알 수 있다.


▷ 단방향

아래 이미지에서 주 테이블은 Member, 대상 테이블은 Locker이다.

@Entity
public class Member {

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

    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private Locker locker;
}
@Entity
public class Locker {

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

    private String name;
}

일대일 관계이므로 객체 매핑에 @OneToOne을 사용했고 데이터베이스에는 LOCKER_ID에

유니크(UNI) 조건을 추가했다. 참고로 이 관계는 다대일 단방향과 거의 비슷하다.


특징

• 주 객체가 대상 객체의 참조를 가지고 있음

• 객체지향 개발자 선호

• JPA 매핑 편리

• 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인 가능

• 값이 없으면 외래 키에 null 허용


▷ 양방향

@Entity
public class Member {

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

    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private Locker locker;
}
@Entity
public class Locker {

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

    private String name;

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

양방향 관계이므로 연관관계의 주인을 정해야 한다. MEMBER 테이블이 외래 키를 가지고

있으므로 Member 엔티티에 있는 Member.locker가 연관관계의 주인이다.

따라서 반대 매핑인 사물함의 Locker.member는 mappedBy를 선언해서 연관관계의 주인이

아니라고 설정했다.


대상 테이블의 외래 키

전통적인 데이터베이스 개발자들은 보통 대상 테이블에 외래 키가 있는 것을 선호한다.

이 방법의 장점은 테이블 관계를 일대일에서 다대일로 변경할 때 테이블 구조를 그대로

유지할 수 있다.

위의 그림을 보면 일대일 관계 중 대상 테이블에 외래 키가 있는 단방향 관계는

JPA에서 지원을 해주지 않는다. 그리고 이런 모양으로 매핑할 수 있는 방법도 없다.

이때는 단방향 관계를 Locker에서 Member 방향으로 수정하거나, 양방향 관계로

만들고 Locker를 연관관계의 주인으로 설정해야 한다.


▷ 양방향

@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;
    
}

일대일 매핑에서 대상 테이블에 외래 키를 두고 싶으면 이렇게 양방향으로 매핑한다.

주 엔티티인 Member 엔티티 대신에 대상 엔티티인 Locker를 연관관계의 주인으로

만들어서 LOCKER 테이블의 외래 키를 관리하도록 했다.


특징

• 대상 테이블에 외래 키가 존재

• 전통적인 데이터베이스 개발자 선호

• 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조 유지

프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩됨.


다대다 [N:M]

실무에서 쓰면 안된다!!


다대다 매핑이란?

• 관계형 데이터베이스는 정규화된 테이블을 2개로 다대다 관계를 표현할 수 없음.

• 연결 테이블을 추가해서 일대다, 다대일 관계로 풀어내야 한다.

객체는 컬렉션을 사용해서 객체 2개로 다대다 관계 가능.


@ManyToMany 사용

@JoinTable 사용

• 단방향, 양방향 가능

▷ 단방향

@Entity
public class Member {

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

    @ManyToMany
    @JoinTable(name = "MEMBER_PRODUCT")
    private List<Product> products = new ArrayList<>();
}
@Entity
public class Product {

    @Id @GeneratedValue
    private Long id;

    private String name;
}


▷ 양방향

@Entity
public class Member {

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

    @ManyToMany
    @JoinTable(name = "MEMBER_PRODUCT")
    private List<Product> products = new ArrayList<>();
}
@Entity
public class Product {

    @Id @GeneratedValue
    private Long id;

    private String name;

    @ManyToMany(mappedBy = "products")
    private List<Member> members = new ArrayList<>();
}


다대다 매핑의 한계

편리해 보이지만 실무에서 사용하지 않는다.

• 연결 테이블이 단순히 연결만 하고 끝나지 않음

• 중간 테이블에 추가적인 필드가 들어올 수 있음.


다대다 매핑의 한계 극복

• 연결 테이블을 엔티티로 작성한다.(중간 테이블을 엔티티로 승격)

• @ManyToMany → @OneToMany, @ManyToOne


예시 코드

MemberProduct(ORDERS)라는 테이블과 엔티티를 만든것을 볼 수 있다.

@Entity
public class Member {

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

    @OneToMany(mappedBy = "member")
    private List<MemberProduct> memberProducts = new ArrayList<>();
}
@Entity
@Table(name = "ORDERS")
public class MemberProduct {
    
    @Id @GeneratedValue
    @Column(name = "ORDER_ID")
    private Long id;

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

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

    private int count;
    private int regDate;
}
@Entity
public class Product {

    @Id @GeneratedValue
    private Long id;

    private String name;

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