1. 연관관계 매핑 시 고려사항 3가지
1) 다중성 - 관련 어노테이션 기억!!
다중성 | 어노테이션 | 주의사항 |
N:1 | @ManyToOne | |
1:N | @OneToMany | |
1:1 | @OneToOne | |
N:M | @ManyToMany | 실무에서 거의 사용 X |
2) 단방향, 양방향
테이블
- 외래키 하나로 양쪽 조인 가능 => 사실 방향이라는 개념이 없음
객체
- 참조용 필드가 있는 쪽으로만 참조 가능
- 한쪽만 참조하면 단방향
- 양쪽이 서로 참조하면 양방향 => 얘도 사실 단방향 + 단방향 : 객체 서로서로에 대한 단방향
3) 연관관계의 주인
- 테이블은 외래 키 하나로 두 테이블이 연관관계를 맺음 -> 객체 양방향 관계는 A->B , B->A 처럼 참조가 2군데
- 객체 양방향 관계는 참조가 2군데 있음!! 따라서 둘중 테이블의 외래 키를 관리할 곳을 지정해야 함
- 연관관계의 주인 : 외래 키를 관리하는 쪽
- 주인의 반대편 : 외래 키에 영향을 주지 않음!! Read Only
4) 꼭! 기억하자
@JoinColumn 에 해당하는 컬럼은 FK 가 있는 컬럼을 의미한다.
따라서 name = "FK컬럼명" 을 매핑해주면 된다
일대다 : 다대일 -1:N, N:1 - 에서 연관관계의 주인은 항상 '다' 쪽이 외래키를 갖고,
'다' 쪽이 연관관계의 주인이 된다
2. 다대일 N:1
1) 단방향
- 가장 흔하고, 가장 많이 사용되는 다중성
- 다대일의 반대는 일대다 N:1 -> 1:N
2) 양방향
- 외래키가 있는 쪽이 양방향 연관관계의 주인
- 양쪽을 서로 참조하도록 개발 -> 단방향이 2개
3. 일대다 1:N
1) 단방향
- 일대다 단방향은 일대다에서 일(1) 쪽이 연관관계의 주인이 됨
- 테이블 일대다 관계는 항상 다(N) 쪽에 외래키가 있음
- 객체와 테이블의 차이 때문에 반대편에서 테이블의 외래 키를 관리하는 특이한 구조
- @JoinColumn 을 꼭 사용해야함!! 그렇지 않으면 조인 테이블 방식을 사용함(중가에 테이블이 하나 추가됨)
엔티티가 관리하는 외래키가 다른 테이블에 존재
연관고나계 고나리를 위해 추가로 update SQL 이 실행됨
따라서 일대다 단방향보다는 다대일 양방향 매핑을 사용이 권장됨!!
2) 양방향
- 사실 양방향 일대다 매핑은 공식적으로 존재하지는 X
- 연관관계의 주인이 아닌 쪽에서 @JoinColumn(insertable=false, updatable=false) 를 사용 => 읽기 전용 필드를 사용해서 양방향 처럼 사용하는 방법
- 그냥 다대일 양방향을 사용하자
4. 일대일 1:1
1) 단방향
- 일대일 관계는 그 반대도 일대일
- 주 테이블이나 대상 테이블 중 외래 키 선택 가능 => 외래키가 있는곳이 연관관계 주인
- DB 에 외래키 제약조건에 유니크 제약조건이 꼭 필요함 => 다대일에서 FK 에 유니크 제약조건이 추가된 경우
쉽게 생각하면 회원 - 회원 사물함의 관계
회원 한 명 당 회원 사물함은 하나여야만 함 => 1:1 관계
LOL 같은 게임에서 이메일 하나 당 계정 하나가 있는 것과 비슷 => 이메일 : 계정 = 1:1
Member
// Member 하나당 Locker 하나
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
Locker
@Entity
public class Locker {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToOne(mappedBy = "locker")
private Member member;
}
2) 양방향
- 다대일 양방향 매핑과 동일하게 FK 가 있는 곳이 연관관계의 주인
- 반대편은 mappedBy 적용
3) 일대일 매핑 정리
주 테이블에 외래 키
- 주 객체가 대상 객체의 참조를 갖는 것처럼 주 테이블에 외래 키를 두고 대상 테이블을 찾음
- 객체 지향 개발자가 선호
- JPA 매핑 관리
- 장점 : 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인 가능
- 단점 : 값이 없으면 외래 키에 null 허용해야함
대상 테이블에 외래 키
- 대상 테이블에 외래 키가 존재
- 전통적인 DB 개발자가 선호
- 장점 : 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조 유지
- 단점 : 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩됨
5. 다대다 N:M
- 관계형 DB 는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없음
=> 연결 테이블을 추가해서 일대다, 다대일 관계로 풀어내야 함
- 그런데!! 객체는 컬렉션을 사용해서 객체 2개로 다대다 관계 가능
- @ManyToMany 어노테이션을 사용함
- @JoinTable 를 사용해서 연결 테이블을 지정함
- 다대다 매핑도 양방향, 단방향 가능
1) 단방향
Member
// 다대다 매핑
// JoinTable 어노테이션을 사용 => name 파라미터에는 join 하는 테이블 명이 들어감 : 없으면 만들어짐
@ManyToMany
@JoinTable(name = "MEMBER_PRODUCT")
private List<Product> products = new ArrayList<>();
2) 양방향
Product 클래스에서 member 를 양방향으로 참조할 수 있도록 List 를 만든다
@Entity
public class Product {
@Id
@GeneratedValue
private Long id;
private String name;
@ManyToMany(mappedBy = "products")
private List<Member> members = new ArrayList<>();
}
3) 다대다 매핑의 한계
편리해보이지만 실무에서 사용 불가능
연결 테이블이 단순히 연결만하고 끝나지 않음
=> JoinTable 가 단순히 join 을 위한 테이블로서 다루기 힘듦 : 주문시간, 수량 같은 데이터가 들어올 수 있음
따라서 JoinTable 을 하나의 Entity 로 승격시켜서 만들어 두면 해결 가능!!
MemberProduct Entity
@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;
}
Member
// 다대다 매핑에서 memberproduct 테이블을 따로 만들어서 일대다, 다대일로 변경!!
@OneToMany(mappedBy = "member")
private List<MemberProduct> memberProducts = new ArrayList<>();
Product
@Entity
public class Product {
@Id
@GeneratedValue
private Long id;
private String name;
// 다대다 매핑에서 memberproduct 테이블을 따로 만들어서 일대다, 다대일로 변경!!
@OneToMany(mappedBy = "product")
private List<MemberProduct> memberProducts = new ArrayList<>();
}
6. 실전 코드로 확인하기
예제 ERD
1) Order
public class Order {
@Id @GeneratedValue
@Column(name = "ORDER_ID")
private Long id;
// 현재 클래스 : MEMBER => 하나의 MEMBER 에 여러 ORDER 이 올 수 있음 -> 1:N
@ManyToOne
@JoinColumn(name="MEMBER_ID")
private Member member;
// 양방향 연관관계 => 현재클래스 : OrderItem = 1: N
@OneToMany(mappedBy = "order")
private List<OrderItem> orderItems = new ArrayList<>();
private LocalDateTime orderDate;
// 주문 상태는 Enum 으로
@Enumerated(EnumType.STRING)
private OrderStatus status;
// 양방향 연관관계 편의 메서드
public void addOrderItems(OrderItem orderItem){
orderItems.add(orderItem);
orderItem.setOrder(this);
}
// Order - delivery 관계에서 order 이 연관관계의 주인
@OneToOne
@JoinColumn(name = "DELIVERY_ID")
private Delivery delivery;
}
2) Item
@Entity
@Table
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Getter
@Setter
public class Item {
@Id
@GeneratedValue
@Column(name = "ITEM_ID")
private Long id;
private String name;
private int price;
private int stockQuantity;
// 다대다 매핑
// joinTable 을 통해서 임의로 테이블 생성 후 join 하게 됨
// 단 이때 연관관계의 주인은 Category 에서 가져갔음으로 여기는 mappedBy 를 사용한다
@ManyToMany(mappedBy = "items")
private List<Category> categories = new ArrayList<>();
}
3) Delivery
@Entity
public class Delivery {
@Id
@GeneratedValue
@Column(name = "delivery_id")
private Long id;
private String city;
private String street;
private String zipcode;
@Enumerated(EnumType.STRING)
private DelverStatus status;
@OneToOne(mappedBy = "delivery")
private Order order;
}
4) Category
@Entity
public class Category {
@Id
@GeneratedValue
@Column(name ="CATEGORY_ID")
private Long id;
private String name;
// JPA 는 자기 자신을 조인 가능 : 부모 카테고리
@ManyToOne
@JoinColumn(name = "PARENT_ID")
private Category parent;
// 자식 카테고리
@OneToMany(mappedBy = "parent")
private List<Category> child = new ArrayList<>();
// 다대다 매핑
// joinTable 을 통해서 임의로 테이블 생성 후 join 하게 됨
// 이때 joinColumns 와 inverseJoinColumns 를 사용하는데
// 각각 joinColumns 는 현재 자신의 엔티티에서 조인하는 joinColumn 을 넣어주고
// inverseJoinColumns 에는 반대 - 다른 Many 에 해당하는 엔티티 - 엔티티에서 조인하는 joincolumn 을 넣어주면 된다 @ManyToMany
@ManyToMany
@JoinTable(name = "CATEGORY_ITEM",
joinColumns = @JoinColumn(name = "CATEGORY_ID"),
inverseJoinColumns = @JoinColumn(name = "ITEM_ID")
)
private List<Item> items = new ArrayList<>();
}
7. 다양한 연관관계 매핑 정리 : 사용하는 어노테이션과 속성
@JoinColumn : 외래 키를 매핑할 때 사용
속성 | 설명 | 기본값 |
name | 매핑할 외래키 이름 | 필드명_참조 테이블 PK 컬럼명 |
referencedColumnName | 외래키가 참조하는 대상 테이블의 컬럼명 | 참조하는테이블의 기본키 컬럼명 |
foreignKey(DDL) | 외래키 제약조건을 직접 지정 가능 테이블 생성 시에만 사용 |
|
unique nullable insertable updateable columnDefinition table |
@Column 의 속성과 동일 |
@ManyToOne 의 주요 속성
속성 | 설명 | 기본값 |
optional | false 로 설정 시 연관된 엔티티가 항상 있어야 함 | TRUE |
fetch | 글로벌 페치 전략을 설정 - 지연로딩, 즉시로딩 |
|
cascade | 영속성 전이 기능 사용 | |
targetEntity | 연관된 엔티티의 타입 정보를 설정. 이 기능은 거의 사용 X 컬렉션을 사용해도 제네릭 타입 정보를 확인 가능 |
- Reference
'Java - SpringJPA' 카테고리의 다른 글
Spring JPA (8) - JPA 프록시와 연관관계 관리, 즉시로딩, 지연로딩, Cascade, 고아 객체 (0) | 2022.10.11 |
---|---|
Spring JPA (7) - JPA 고급 매핑 : 상속관계 매핑, @mappedsuperclass (0) | 2022.10.04 |
Spring JPA (5) - JPA 개념 잡기 : 단방향 연관관계, 양방향 연관관계, 연관관계의 주인 (0) | 2022.10.03 |
Spring JPA (4) - JPA 개념 잡기 : 객체-테이블 매핑, 필드와 칼럼, 기본키 매핑 (0) | 2022.10.02 |
Spring JPA (3) - JPA 개념 잡기 : 영속성 컨텍스트, 플러시, 준영속 상태 (0) | 2022.10.01 |
댓글