관리 메뉴

나구리의 개발공부기록

다양한 연관관계 매핑, 다대일[N:1], 일대다[1:N], 일대일[1:1], 다대다[N:M] 실전 예제 - 다양한 연관관계 매핑 본문

인프런 - 스프링부트와 JPA실무 로드맵/자바 ORM 표준 JPA 프로그래밍 - 기본편

다양한 연관관계 매핑, 다대일[N:1], 일대다[1:N], 일대일[1:1], 다대다[N:M] 실전 예제 - 다양한 연관관계 매핑

소소한나구리 2024. 10. 10. 10:26

출처 : 인프런 - 자바 ORM 표준 JPA 프로그래밍 - 기본편(유료) / 김영한님  
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용


** 실전예제 전까지는 DB URL 설정을 test로 설정하여 실습

<property name="jakarta.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/>

1. 다양한 연관관계 매핑

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

(1) 다중성

  • 다대일: @ManyToOne
  • 일대다: @OneToMany
  • 일대일: @OneToOne
  • 다대다: @ManyToMany
  • 다대다는 실무에서 사용하지 않고 다른방식으로 대체해서 사용해야 함

(2) 단방향, 양방향

  • 테이블은 외래 키 하나로 양쪽 조인이 가능하며 방향이라는 개념이 없음
  • 객체는 참조용 필드가 있는 쪽으로만 참조가 가능하며 한쪽만 참조하면 단방향, 양쪽이 서로 참조하면 양방향
  • 즉 객체의 양방향 참조는 각 객체가 서로 단방향으로 참조하는 것임(단방향 참조가 2개라고 이해하면 됨)

(3) 연관관계의 주인

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

1. 다대일[N:1]

1) 다대일 단방향

  • 가장 많이 사용하는 연관관계이며 다대일의 반대는 일대다가 됨
  • 테이블에서는 항상 다쪽에 외래키가 있으므로 Member Entity에서 외래키에 해당하는 필드에 연관관계를 매핑하면 됨

2) 다대일 양방향

  • 외래 키가 있는 쪽이 연관관계의 주인이 되어야 하므로 Member 엔터티의 team 필드가 연관관계의 주인이 됨
  • 다대일 양방향 매핑에서는 항상 다쪽이 연관관계의 주인이 되어야 함
  • Team 엔터티에서는 일대다 매핑을 하고 mappedBy속성을 걸어서 team 필드에 매핑되었다고 지정하면 됨


3. 일대다 [1:N]

1) 일대다 단방향 

  • 표준 스팩에서 지원하긴하지만 실무에서 이런 설계는 권장하지 않음
  • 일대다 관계에서 일쪽이 연관관계의 주인이 되는 케이스이며 객체 중심적으로 설계를 할 때에는 이런 관계가 잘 나올 확률이 높지만 DB 입장에서 생각해보면 무조건 다쪽에 외래키가 이러한 설계가 일반적이지 않은 상황임
  • 일대다에서 일(1)쪽에 외래키를 관리하는 연관관계의 주인이 있고 다(N)쪽에 외래키가 서로 반대편에 있는 특이한 구조
  • @JoinColumn을 꼭 사용해야하며, 사용하지 않으면 중간에 두 테이블을 매핑하는 중간 테이블을 하나 생성하게됨(@JoinTable의 기능이 실행됨)


(1) Team 수정

  • Team과 Member를 일대다로 매핑
  • @JoinColumn으로 MEMBER테이블에 TEAM_ID필드를 외래키로 설정
@Entity
public class Team {

    // ... 기존 코드 생략

    // 일대다 단방향 연관관계 매핑
    // MEMBER 테이블의 TEAM_ID를 연관관계 매핑함
    @OneToMany
    @JoinColumn(name = "TEAM_ID")
    private List<Member> members = new ArrayList<>();
    
    // ... getter, setter
}

 

(2) JpaMain 수정 후 실행

  • JpaMain에서 Member와 Team을 생성하고 각 값을 입력한뒤 외래키 값을 입력하면 Member와 Table에 값을 입력하는 insert쿼리 2번 외에도 update 쿼리가 한번더 실행됨
  • 외래키가 MEMBER 테이블에 있다보니 TEAM 테이블을 저장시 매핑된 외래키를 저장하려면 어쩔수없이 MEMBER테이블의 TEAM_ID 필드를 update쿼리로 수정해야함
  • 처음 Member 엔터티의 insert쿼리를 보면 TEAM_ID 필드에 값을 입력하는 쿼리가 없음
  • 이렇게 update쿼리가 한번더 실행되는 것은 성능에 큰 문제는 없을지라도 Team 엔터티를 수정했는데 MEMBER 테이블이 update가 되는 쿼리를 보면 JPA를 잘 아는 실무자라도 헷갈리게 될 가능성이 높음
  • 특히 실무에서는 테이블이 수십개이상이 돌아가는 상황에서는 더욱 유지보수하기가 어려워짐
public class JpaMain {
    public static void main(String[] args) {
    
    // 기존 코드 생략
     try {
            Member member = new Member();
            member.setUsername("MemberA");
            em.persist(member);

            Team team = new Team();
            team.setName("TeamA");
            team.getMembers().add(member);
            em.persist(team);

            tx.commit();
   // 기존 코드 생략
}

좌) 정상적으로 insert쿼리가 2번 실행 / 우) Member에 대한 Update쿼리가 한번더 실행된 모습

 

(3) 일대다 단방향의 단점 및 정리

  • 엔터티가 관리하는 외래 키가 다른 테이블에 있음 -> 가장 명확한 단점
  • 연관관계 관리를 위해 추가로 UPDATE SQL이 실행됨
  • 일대다 단방향 매핑을 사용하지말고 객체지향적인 설계를 조금 내려놓더라도 다대일 양방향 매핑을 사용하는 것이 유지보수가 훨씬 수월함

2) 일대다 양방향

  • Team이 연관관계의 주인이이고 외래키는 MEMBER 테이블이 가지고있는 상황(위의 일대다 매핑이 이루어진 상황)에서 Member 엔터티에서도 Team을 조회하고자 하는 상황이 발생했다고 가정
  • 공식적으로 존재하는 스펙은 아니지만 @JoinColumn에 속성을 추가하여 강제로 읽기 전용 필드를 만들어 양방향 매핑이 된 것 처럼 사용할 수 있음
  • 가끔가다 이러한 상황이 마주칠 수 있는데 마찬가지로 권장하지 않으므로 애초에 다대일 양방향을 사용하는 것이 좋음

(1) Member 수정

  • Member엔터티와 Team 엔터티를 다대일 매핑
  • @JoinColumn(name = "TEAM_ID", insertable = false, updatable = false)으로 insert와 update를 강제로 무효화 시켜서 읽기 전용으로 만들어 강제로 양방향이 되도록 하는 것
package jpabook.jpashoptest.domain;

@Entity
public class Member {
    //... 기존코드 동일
    
    @ManyToOne
    @JoinColumn(name = "TEAM_ID", insertable = false, updatable = false)
    private Team team;

    // ...getter, setter
}

4. 일대일 [1:1]

1) 일대일 관계 특징

  • 일대일 관계는 그 반대도 일대일
  • 일대일 관계이기 때문에 주 테이블이나 대상 테이블 중에 외래키를 가질 대상을 선택할 수 있음
  • 외래키에 데이터베이스 유니크 제약조건을 추가해야함, 제약조건이 없어도 일대일 관계를 할 수 있지만 애플리케이션에서 관리를 잘 해야함

** 비즈니스 상황 가정

  • 멤버(Member)는 사물함(Locker)을 하나만 사용할 수 있고 사물함도 하나의 멤버만 가질 수 있다는 비즈니스 룰이 있다고 가정
  • MEMBER 테이블을 주 테이블, LOCKER 테이블을 상대 테이블이라고 가정

(1) Locker 생성

@Entity
public class Locker {

    @Id @GeneratedValue
    @Column(name = "LOCKER_ID")
    private Long id;
    private String name;
    
    // ... getter, setter   
}

2) 일대일: 주 테이블에 외래 키

  • 주 테이블인 MEMBER에 외래키와 유니크 제약조건이 있을때의 일대일 관계를 설정
  • 제일 심플하게 맺는 일대일 연관관계

좌) 일대일 관계에서 주테이블에 외래키가 있을때 단방향 관계도 / 우) 양방향 관계도

 

(1) 단방향, Member 수정

  • 다대일 단방향 매핑과 유사한 방식으로 Member 객체에서 @OneToOne으로 일대일 매핑
@Entity
public class Member {
    // ... 기존 코드 동일

    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private Locker locker;
    
    // ... 
}

 

(2) 양방향, Locker 수정

  • 다대일 양방향 매핑 처럼 반대편에도 일대일 매핑
  • 외래 키가 있는 곳이 Member가 연관관계의 주인이 되도록 반대편인 Locker에 mappedBy를 적용
package jpabook.jpashoptest.domain;

@Entity
public class Locker {

    // ... 기존 코드 동일

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

    // ...
}

2) 일대일: 대상 테이블에 외래 키

  • 상대 테이블인 LOCKER에 외래키와 유니크 제약조건이 있을때의 일대일 관계를 설정

좌) 일대일 관계에서 상대테이블에 외래키가 있을때 단방향 관계도 / 우) 양방향 관계도

 

(1) 단방향

  • 마치 위에서 했던 일대일 관계의 단방향 처럼 외래키를 관리하는 연관관계의 주인은 Member고 LOCKER가 외래키를 가지고 있는 상황과 유사함
  • 이러한 연관관계 설정은 JPA에서 지원하지 않음 

(2) 양방향

  • 주 테이블에 외래 키가 있을 때의 양방향 매핑 방법과 동일한 방법으로 적용
  • 연관관계의 주인과 외래키 설정의 위치만 서로 바꿔주면 됨
  • mappedBy를 Member에, @JoinColumn을 Locker에 설정하면 됨

3) 일대일 정리

  • 어느 테이블에 외래키를 설정해야하는지는 데이터베이스 관리자와 충분히 논의 후에 결정을 해야함
  • 너무 먼 미래까지의 상황을 고려하기보다는 적절하게 선택을 하는 것이 좋음

(1) 주 테이블에 외래 키

  • 주 객체가 대상 객체의 참조를 가지는 것처럼 주 테이블에 외래 키를 두고 대상 테이블을 찾음
  • JPA 매핑이 편리하고 하여 객체지향 개발자(ORM)가 선호하는 방식
  • 보통 주 테이블은 조회가 많이 발생하는데 주테이블의 조회하는 쿼리만으로 조인없이 연관관계가 설정되어있는 테이블의 값이 있는지 없는지에 대한정보를 가져올 수 있는 등의 장점이 존재함
  • 단점으로는 값이 없으면 외래 키에 null을 허용하게 되어 DBA 관점에서는 좋은 설계가 아닐 수 있음

(2) 대상 테이블에 외래키

  • 대상 테이블에 외래 키가 존재함
  • 전통적인 데이터베이스 개발자가 선호함
  • 미래의 비즈니스 로직이 변경되어 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조를 유지할 수 있는 장점이 있음
  • JPA 설계 특성 상 무조건 양방향 설계를 해야하며, JPA 프록시 기능의 한계로 지연로딩으로 설정해도 항상 즉시 로딩되는 문제가 발생됨 -> 이후에 자세히 다룸
  • 위 상황에 대해 간단히 축약 해보자면 JPA구현체인 하이버네이트는 프록시 방식으로 동작하는데 프록시 객체를 생성할 때 연관관계가 설정된 필드에 값이 있는지 없는지 알아야 함
  • 지금과 같은 구조에서는 외래키가 있는 테이블을 조회하는 쿼리를 날려봐야 값을 알 수 있어서 무조건 쿼리가 나가게 되고 지연로딩을하면 쿼리만 한번더 나가게되어 N+1쿼리 문제가 발생할 수 있으므로 지연로딩 설정을 하는 의미가 없어지게 됨

** 참고

  • 주 테이블은 비즈니스 관점에서 더 주요한 테이블을 뜻함
  • 게시판을 구성하는 비즈니스에서 게시판테이블과 첨부파일이 테이블이 존재한다면 게시판이 주 테이블이 되는 개념

5. 다대다 [N:M]

1) 테이블과 객체의 다대다 매핑

  • 관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없음
  • 연결 테이블을 추가해서 일대다, 다대일 관계로 풀어내야함
  • 객체는 컬렉션을 사용해서 객체 2개로 다대다 관계가 가능함
  • 객체의 다대다 연관관계의 설정방법은 @ManyToMany를 사용하여 연관관계를 맺고, @JoinTable을 사용하여 중간에 연결 테이블을 생성하여 테이블 구조는 일대다, 다대일 관계로 생성되도록 하면 됨
  • 다대다 매핑은 단방향, 양방향 모두 가능함

좌) 테이블은 다대다 관계가 안됨, 우) 객체는 다대다 관계가 됨

2) 다대다 매핑 실습

(1) Product 생성

package jpabook.jpashoptest.domain;

@Entity
public class Product {

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

    // ...getter, setter
}

 

(2) Member 수정

  • @ManyToMany를 사용하여 다대다 Product와 단방향 매핑 
  • 다대다 매핑은 JoinColumn이아니라 JoinTable을 사용하여 테이블 명을 지정
  • 실행해보면 다대다 관계 사이에 MEMBER_PRODUCT라는 테이블이 생성됨

package jpabook.jpashoptest.domain;

@Entity
public class Member {
    // 기존 코드 생략
	
    @ManyToMany
    @JoinTable(name = "MEMBER_PRODUCT")
    private List<Product> products = new ArrayList<>();
    
    // ... getter, setter
}

 

(3) 양방향 적용 - Product 수정

  • 기존의 Member에 적용한 다대다 관계를 유지한채 Product에도 @ManyToMany를 사용하여 다대다 관계를 맺으면 양방향 관계가 설정 됨
  • 연관관계 주인을 설정해줘야 하므로 mappedBy를 products로 지정
@Entity
public class Product {
    // 기존코드 생략
   
    @ManyToMany(mappedBy = "products")
    private List<Member> members = new ArrayList<>();

    // ...getter, setter
}

3) 다대다 매핑의 한계

  • 편리해 보이지만 실무에서 사용 하는것을 권장하지 않음
  • 연결 테이블이 단순히 연결만 하고 끝나지가 않고 실무에서는 이렇게 단순하게 운영되지 않음
  • 중간 테이블을 운영할 때 주문시간, 수량, 변경정보 등등 수많은 정보들이 필요하게 되는데 다대다 매핑의 중간테이블 생성은 이러한 추가정보를 입력할 수 없음
  • 중간 테이블이 자동으로 생성되어 있기 때문에 생각하지 못한 쿼리가 발생할 가능성도 있음

4) 다대다 문제 해결 

  • 연결 테이블용 엔터티를 추가하여 다대일 관계를 형성
  • 즉 다대다 매핑 시 자동으로 생성되었던 연결테이블 역할을하는 테이블을 엔터티로 생성하여 다대다 관계의 중간 테이블 역할을 하게 만들어서 해당 테이블과 다대일 관계를 맺는 것
  • 다대다 관계설정이 필요하면 이러한 방식으로 설계하는 것을 권장함

연결 테이블 엔터티를 만들어서 해당 테이블을 중심으로 다대일 관계를 형성하여 문제를 해결

 

(1) MemberProduct 생성

  • Member와 Product의 중간 역할을 하는 MemberProduct를 생성하여 각각 엔터티들과 다대일 연관관계를 맺도록 설정하고 연관관계의 주인은 중간역할을 하는 MemberProduct가 외래키를 가지고 있으므로 MemberProduct로 설정
  • @JoinColumns로 외래키를 매핑
  • 지금은 MemberProduct라고 엔터티를 생성 테이블명이 ORDERS라고 되어있지만, 애초에 비즈니스 로직에서 이런 엔터티가 필요할 가능성이 높기때문에 엔터티명 자체를 Order로 지정하는 것이 더 좋음
  • Order엔터티는 관계형 DB에서 관례적으로 ORDERS로 많이 사용함 (order by 예약어 때문)

** 참고

  • 전통적인 방식에서는 이런 중간테이블의 PK를 FK인 MEMBER_ID와 PRODUCT_ID를 묶어서 복합키로 설정하여 PK로 지정하는 경우도 많이 있어는데 김영한님의 실전 누적 경험과 여러가지 고민을 한 결과로는 이러한 방식보다 PK는 웬만하면 비즈니스 적으로 의미없는 값을 쓰는것을 권장한다고함
  • 애플리케이션은 계속 변경되는데 PK가 무언가에 종속이 되어있으면 개발 및 유지보수에 유연함이 떨어진다고 함
  • 무엇이 좋을지 계속 고민하는 연습을 권장
package jpabook.jpashoptest.domain;

@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 orderAmount;
    private LocalDateTime orderDatetime;

    // ... getter, setter
}

 

(2) Member, Product 수정

  • MemberProduct의 필드와 일대다 연관관계를 맺고 연관관계의 주인을 모두 MemberProduct의 필드로 지정
@Entity
public class Member {
    // 기존 코드 생략

    // 다대다 -> 일대다로 변경, 연관관계의 주인을 member로 설정
    @OneToMany(mappedBy = "member")
    private List<MemberProduct> memberProducts = new ArrayList<>();
    
}

@Entity
public class Product {
    // 기존 코드 생략
    
    // 다대다 -> 일대다로 변경, 연관관계의 주인을 product로 설정
    @OneToMany(mappedBy = "product")
    private List<MemberProduct> memberProducts = new ArrayList<>();

}

6. 실전 예제 - 다양한 연관관계 매핑

** jdbc.url 설정을 jpashop으로 변경해서 진행

<property name="jakarta.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/jpashop"/>

1) 배송, 카테고리 추가

  • 실무에서는 다대다를 안쓰지만, 예제이기도하고 공식 스펙이기도해서 다대다 관계를 넣어서 예제를 실습
  • @ManyToMany는 필드를 더 추가할 수 없고 엔터티 테이블이 불일치 되는 제약이 너무 많음
  • 실전에서는 중간테이블이 단순하지 않기때문에 꼭 별도의 엔터티로 일대다, 다대일을 사용하는 것을 권장 - 반복 강조

(1) Entity

  • 주문과 배송은 1:1, @OneToOne
  • 상품과 카테고리는 N:M. @ManyToMany

좌) 엔터티 관계도 / 우) 엔터티 상세 관계도

 

(2) ERD

  • 카테고리와 아이템의 관계가 다대다이므로 @JoinTable의 기능을 활용하여 중간 매핑 테이블이 생성됨
  • 테이블 관계에서는 중간 매핑 테이블과 카테고리와 아이템 테이블이 일대다, 다대일 관계로 형성됨

2) 코드

  • 양방향 설정 시 양방향 편의 메서드를 만드는데, 지금 예제에서는 제외함

(1) Delivery 생성

  • DeliveryStatus는 Enum으로 만들어서 배송 상태를 설정 - 해당 코드는 지금 중요하지 않으므로 생략
  • Order와 연관관계를 일대일 연관관계를 설정하고 Order의 delivery를 연관관계 주인으로 설정
package jpabook.jpashop.domain;

@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 DeliveryStatus status;

    @OneToOne(mappedBy = "delivery")
    private Order order;

}

 

(2) Order 수정

  • Delivery와 일대일 연관관계를 설정하고 DELIVERY_ID를 외래키로 지정
@Entity
@Table(name = "ORDERS") // 주문은 ORDERS로 관례상 많이 씀
public class Order {
    // ... 기존 코드 생략
    
    @OneToOne
    @JoinColumn(name = "DELIVERY_ID")
    private Delivery delivery;

    // ... 기존 코드 생략

}

 

(3) Category 생성

  • Item과 다대다 연관관계로 매핑
  • @JoinTable로 중간 연결 테이블을 생성하고, name 속성으로 테이블의 이름을, joinColumns로 현재 엔터티의 외래키의 매핑을, inverseJoinColumns로 반대편 연관관계 엔터티의 외래키의 매핑을 설정
  • 나 자신을 다대일, 일대다로 셀프 매핑하여 계층 구조를 설정하는 parent, child 구조도 생성 -> 지금 수업에서의 중요 포인트는 아니여서 나 자신을 매핑하여 계층 구조로 만들 수 있구나 정도로 이해하면 됨
package jpabook.jpashop.domain;

@Entity
public class Category {

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

    @ManyToMany
    @JoinTable(name = "CATEGORY_ITEM",      // 테이블 이름 지정
                joinColumns = @JoinColumn(name = "CATEGORY_ID"),    // 카테고리의 외래키 매핑
                inverseJoinColumns = @JoinColumn(name = "ITEM_ID")  // 반대편(ITEM)의 외래키 매핑
    )
    private List<Item> items = new ArrayList<>();

    // 나 자신을 매핑하는 것 -> 이후에 배움
    @ManyToOne
    @JoinColumn(name = "PARENT_ID")
    private Category parent;

    @OneToMany(mappedBy = "parent")
    private List<Category> child = new ArrayList<>();
    
    // ...getter, setter
}

 

(4) Item 수정

  • Category와 다대다 매핑을 하고 Category의 items 필드를 연관관계 주인으로 설정
@Entity
public class Item {
    // 기존 코드 생략

    @ManyToMany(mappedBy = "items")  // 다대다 양방향 연결, 연관관계의 주인을 Category의 items로 설정
    private List<Category> categories = new ArrayList<>();

    // 기존 코드 생략
}

 

3) 매핑 애노테이션 속성 정리

(1) @JoinColumn

  • 외래 키를 매핑할 때 사용
속성 설명 기본값
name 매핑할 외래 키의 이름 필드명 + _ + 참조하는 테이블의 기본키 컬럼명
referencedColumnName 외래 키가 참조하는 대상 테이블의 컬럼명 참조하는 테이블의 기본키 컬럼명
foreignKey(DDL) 외래 키 제약조건을 직접 지정(테이블 생성할 때만 사용)  
uqique
nullable insertable
updatable
columnDefinition
table
@Column의 속성과 같음 - 아래에 기능 설명
https://nagul2.tistory.com/331
 


(2) @ManyToOne - 주요 속성

  • 다대일 관계 매핑할 때 사용
  • mappedBy 속성이 없음, JPA에서는 @ManyToOne을 사용하면 무조건 연관관계의 주인이 되어야 한다고 보면 됨
속성 설명 기본값
optional false로 설정하면 연관된 엔터티가 항상 있어야 함 TRUE
fetch 글로벌 페치 전략을 사용 @ManyToOne = FetchType.EAGER
cascade 영속성 전이 기능을 사용  
targetEntity 연관된 엔터티의 타입 정보를 설정
제네릭 기능이 나온이후로 거의 사용안함
 

 

(3) @OneToMany - 주요 속성

  • 일대다 관계 매핑할 때 사용
  • mappedBy로 연관관계의 주인필드를 지정할 수 있음
속성 설명 기본값
mappedBy 연관관계의 주인 필드를 선택  
fetch 글로벌 페치 전략을 사용 @OneToMany = FetchType.LAZY
cascade 영속성 전이 기능을 사용  
targetEntity 연관된 엔터티의 타입 정보를 설정
제네릭 기능이 나온이후로 거의 사용안함
 

 

** 참고

  • optional, fetch, cascade의 설명은 뒤에서 자세히 설명함