[스프링 JPA] 다양한 연관관계

sonnng·2023년 9월 30일
0

Spring

목록 보기
13/41

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

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



다중성

  • 다대일, 일대다, 일대일, 다대다가 있다.

단방향, 양방향

  • [테이블] 외래키 하나로 양쪽 조인이 가능하며 두 테이블이 연관관계를 맺는다. 테이블에서는 방향이라는 개념이 없다.
  • [객체] 참조용 필드가 있는 쪽으로만 참조가능, 한쪽만 참조하면 단방향, 서로 참조하면 양방향이다. 양방향관계는 참조가 2군데로, 외래키를 관리할 곳을 지정해주어야 한다.
  • 연관관계의 주인은 외래키를 관리할 수 있으며, 주인이 아닌 쪽은 조회만 가능하다.



1. 다대일 단방향

N쪽 : 외래키가 있는 쪽으로 = 연관관계의 주인이다.

[테이블] : 항상 N쪽에 외래키가 존재하도록 설계
[객체] : N쪽에 참조할 객체 필드 선언(테이블과 연관관계 매핑)

<객체 연관관계>
Member(Long id, Team team, String username)
Team(Long id, String name)

<테이블 연관관계>
Member(MEMBER_ID(PK), USERNAME, TEAM_ID(FK))
TEAM(TEAM_ID(PK), NAME)

-------객체 Member의 Team team와 테이블 MEMBER의 TEAM_ID가 연관관계 매핑-------

예시) N(Member) : 1(Team)

Member class...

@Id @GeneratedValue //생략하면 AUTO
@Column(name = "MEMBER_ID")
private Long id;
private String username;

@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
Team class...

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



2. 다대일 양방향

  • 1번 다대일 단방향 코드 중 객체 Team에 List만 추가해주면 된다.
  • 다대일 단방향의 테이블 구조 동일

<객체 연관관계>
Member(Long id, Team team, String username)
Team(Long id, String name, List members)

<테이블 연관관계>
Member(MEMBER_ID(PK), USERNAME, TEAM_ID(FK))
TEAM(TEAM_ID(PK), NAME)

-------객체 Member의 Team team와 테이블 MEMBER의 TEAM_ID가 연관관계 매핑-------

예시) N(Member) : 1(Team)

Member class...

@Id @GeneratedValue //생략하면 AUTO
@Column(name = "MEMBER_ID")
private Long id;
private String username;

@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
Team class...

@Id
@GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();



3. 일대다 단방향(권장X)

  • 1쪽 : 연관관계의 주인

  • N쪽 : (테이블)외래키를 가진다.

  • JOIN COLUMN을 꼭 1쪽에 작성해주어야 한다.

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

<객체 연관관계>
Member(Long id, String username)
Team(Long id, String name, List members)

<테이블 연관관계>
Member(MEMBER_ID(PK), USERNAME, TEAM_ID(FK))
TEAM(TEAM_ID(PK), NAME)

-------객체 Team의 List members와 테이블 MEMBER의 TEAM_ID가 연관관계 매핑-------

예시) N(Member) : 1(Team) .. 1쪽에서 연관관계의 주인으로 설정된다.

Member class...

@Id @GeneratedValue //생략하면 AUTO
@Column(name = "MEMBER_ID")
private Long id;
private String username;
Team class...

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

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

객체의 TEAM(1쪽)과 테이블의 MEMBER의 TEAM_ID와 연관관계 매핑이 된다.
반대편 테이블(MEMBER)의 외래키를 관리하는 특이한 구조가 된다.

-> 일대다 대신, 필요할 경우 다대일 단방향을 다대일 양방향으로 바꾸어서 사용하도록 한다.

  • 일대다 단방향의 단점 : 엔티티가 관리하는 외래키가 다른 테이블에 있다/연관관계 관리를 위해 UPDATE SQL 실행



4. 일대다 양방향(권장X)

  • 공식적으로 이러한 매핑은 존재x

  • @JoinColumn(insertable = false, updatable = false) : MEMBER(N쪽)에 매핑이 되는 컬럼에 작성해준다. TEAM과 MEMBER에 모두 @JoinColumn을 작성, 이대로 두면 모두 연관관계 주인이 되지만, MEMBER에는 insertable = false, updatable = false를 추가해주면 읽기전용이 된다. 따라서 위의 속성을 추가

<객체 연관관계>
Member(Long id, String username, Team team)
Team(Long id, String name, List members)

<테이블 연관관계>
Member(MEMBER_ID(PK), USERNAME, TEAM_ID(FK))
TEAM(TEAM_ID(PK), NAME)

-------객체 Team의 List members와 테이블 MEMBER의 TEAM_ID가 연관관계 매핑-------

예시) N(Member) : 1(Team) .. 1쪽에서 연관관계의 주인으로 설정된다.

Member class...

@Id @GeneratedValue //생략하면 AUTO
@Column(name = "MEMBER_ID")
private Long id;
private String username;

@ManyToOne
@JoinColumn(name = "TEAM_ID", insertable = false, updatable = false) //읽기전용
private Team team;
Team class...

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

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

-> 결론 : 다대일 양방향을 사용




5. 일대일 관계

  • 주 테이블에 외래키를 넣거나 or 대상 테이블에 외래키를 넣는 방법이 있다.

  • 외래키에 데이터베이스 유니크 제약조건을 추가되어야 일대일 관계가 된다.

  • 단방향, 양방향인 경우는 다대일 관계와 유사하게 작성하면 된다.

  • 연관관계의 주인이 되는 쪽 : 외래키가 있는 곳

[단방향, 주테이블에 외래키 설정]

가장 단순한 방식은 아래와 같이 설정하면 된다.

  • 주 테이블(MEMBER)에 외래키를 갖도록 하는 것이 강사의 방식.
  • 테이블(MEMBER)에는 외래키(LOCKER_ID)가 있고 유니크 제약조건이 설정

<객체 연관관계>
Member(Long id, Locker locker, String username)
Locker(Long id, String name)

<테이블 연관관계>
Member(MEMBER_ID(PK), LOCKER_ID(FK, UNI), USERNAME)
LOCKER(LOCKER_ID(PK), NAME)

-------객체 Member의 Locker locker와 테이블 MEMBER의 LOCKER_ID가 연관관계 매핑-------

예시) 1(Member) : 1(Locker) .. 외래키가 있는 곳을 연관관계의 주인으로 설정된다.

Member class...

@Id @GeneratedValue //생략하면 AUTO
@Column(name = "MEMBER_ID")
private Long id;
private String username;

@ManyToOne
@JoinColumn(name = "TEAM_ID", insertable = false, updatable = false) //읽기전용
private Team team;

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

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

[양방향, 주테이블에 외래키 설정]

  • 외래키가 있는 곳이 연관관계의 주인
  • 반대편은 mappedBy를 적용

<객체 연관관계>
Member(Long id, Locker locker, String username)
Locker(Long id, String name, Member member)

<테이블 연관관계>
Member(MEMBER_ID(PK), LOCKER_ID(FK, UNI), USERNAME)
LOCKER(LOCKER_ID(PK), NAME)

-------객체 Member의 Locker locker와 테이블 MEMBER의 LOCKER_ID가 연관관계 매핑-------

Member class...

@Id @GeneratedValue //생략하면 AUTO
@Column(name = "MEMBER_ID")
private Long id;
private String username;

@ManyToOne
@JoinColumn(name = "TEAM_ID", insertable = false, updatable = false) //읽기전용
private Team team;

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

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

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

[단방향, 대상 테이블에 외래키 설정]

  • 단방향 관계는 JPA에서 지원X
  • 단, 양방향 관계는 지원

[양방향, 대상 테이블에 외래키 설정]

  • 대상 테이블(LOCKER)에 외래키를 갖도록 하는 것이 강사의 방식.
  • 테이블(LOCKER)에는 외래키(MEMBER_ID)가 있고 유니크 제약조건이 설정

<객체 연관관계>
Member(Long id, Locker locker, String username)
Locker(Long id, String name, Member member)

<테이블 연관관계>
Member(MEMBER_ID(PK), USERNAME)
LOCKER(LOCKER_ID(PK), NAME, MEMBER_ID(FK, UNI))

-------객체 Locker의 Member member와 테이블 LOCKER의 MEMBER_ID가 연관관계 매핑-------

[일대일 정리]

  • 강사가 주로 사용하는 방식 : 일대일, 주테이블에 외래키 단방향으로 하는 방법
  1. 주 테이블에 외래키
  • JPA 매핑이 편리
  • 객체지향 개발자가 선호하는 방식
  • 장점 : 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인이 가능
  • 단점 : 값이 없으면 외래키에 null 허용
  1. 대상 테이블에 외래키
  • 어쩔 수 없이 양방향으로 설정하게 됨
  • 전통적인 DBA 개발자에서 선호하는 방식
  • 장점 : 주테이블과 대상 테이블을 일대일-> 일대다 관계로 변경할 때 테이블 구조 유지
  • 단점 : 프록시 기능 한계로 지연로딩으로 설정해도 항상 즉시로딩으로 설정됨

6. 다대다 관계

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

  • 객체 2개로 다대다 관계 설정이 가능

  • 연결 테이블용 엔티티를 추가(연결 테이블을 엔티티로 승격)

<객체 연관관계>
Member(Long id, List memberProducts, String username)
Product(Long id, String name)
MemberProduct(Long id, Member member, Product product, int orderAmount, Date orderDate)

<테이블 연관관계>
Member(MEMBER_ID(PK), USERNAME)
Product(LOCKER_ID(PK), NAME)
Order(ORDER_ID(PK), MEMBER_ID(FK), PRODUCT_ID(FK), ORDERAMOUNT, ORDERDATE)

---객체 Member는 MemberProduct에 대해 @OneToMany 관계, 객체 Product는 MemberProduct에 대해 @OneToMany 관계, MemberProduct는 Member와 Product 각각에 대해 @ManyToOne 관계 ----

Member class...

@Id
@GeneratedValue //생략하면 AUTO
@Column(name = "MEMBER_ID")
private Long id;
private String username;

@ManyToOne
@JoinColumn(name = "TEAM_ID", insertable = false, updatable = false) //읽기전용
private Team team;

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

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

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

@OneToMany(mappedBy = "product")
private List<MemberProduct> memberProductList;

private int count;
private int price;
private LocalDateTime orderDateTime;
MemberProduct class...

@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name=  "MEMBER_ID")
private Member member;
@ManyToOne
@JoinColumn(name = "PRODUCT_ID")
private Product product;

@JoinColumn : 외래키를 매핑할 때 사용하는 어노테이션

[@JoinColumn 속성]

  • referencedColumnName : 외래키가 참조하는 대상 테이블의 칼럼명(default : 참조하는 대상 테이블의 기본키 칼럼명)

  • unique, nullable, insertable, updateable, columnDefinition, table : @Column의 속성과 같다.

  • name : 매핑할 외래키 이름

@ManyToOne - 주요속성

연관관계의 주인이 되므로 mappedBy 속성이 존재하지 않는다

  • optional : false로 설정하면 연관된 엔티티가 항상 있어야 한다
    (default : true)

  • fetch : 글로벌 패치 전략을 설정

    (default : @ManyToOne = FetchType.EAGER, @OneToMany = FetchType.LAZY)

  • cascade : 영속성 전이 기능사용

@OneToMany - 주요속성

  • mappedBy : 연관관계의 주인 필드를 가리키는 역할을 한다.

  • fetch : 글로벌 패치 전략을 설정

    (default : @ManyToOne = FetchType.EAGER, @OneToMany = FetchType.LAZY)

  • cascade : 영속성 전이 기능사용

다양한 연관관계 매핑 연습

  1. 테이블 관계(ERD)

Member(MEMBER_ID(PK), Name, CITY, STREET, ZIPCODE)
ORDERS(ORDER_ID(PK), MEMBER_ID(FK), DELIVERY_ID(FK), ORDERDATE, STATUS)
DELIVERY(DELIVERY_ID(PK), CITY, STREET, ZIPCODE, STATUS)
CATEGORY(CATEGORY_ID(PK), PARENT_ID(FK), NAME)
CATEGORY_ITEM(CATEGORY_ID(FK), ITEM_ID(FK) (PK))

ITEM(ITEM_ID, NAME, PRICE, STOCKQUANTITY)
ORDER_ITEM(ORDER_ITEM_ID(PK), ORDER_ID(FK), ITEM_ID(FK), ORDERPRICE, COUNT)

  1. 엔티티 상세

Member(Long id, String name, String city, String street, String zipcode, List orders)
Order(Long id, Member member, List orderItems, Delivery delivery, Date orderDate, OrderStatus status)
Delivery(Long id, Order order, String city, String street, String zipcode, DeliveryStatus status)
Category(Long id, String name, List items, Category parent, List child)
Item(Long id, String name, int price, int stockQuantity, List categories)
OrderItem(Long id, Item item, Order order, int orderPrice, int count)

  1. 연관관계 설정된 관계
  • Member Deliver 객체들은 Order를 사이에 두고 일대다 이고 Order객체 입장에서는 각 객체들이 일대다 매핑을 하고 있다.
  • Category와 Item은 다대다 매핑을 하고 있으며, 테이블 상에서는 CATEGORY_ITEM을 두도록 한다.
Delivery class...

@Id
@GeneratedValue
@Column(name = "DELIVERY_ID")
private Long id;
@OneToOne(mappedBy = "delivery")
private Orders orders;
Orders class...

@Id
@GeneratedValue
@Column(name = "ORDERS_ID")
private Long id;
@OneToOne
@JoinColumn(name = "DELIVERY_ID")
private Delivery delivery;
private LocalDateTime orderDate;
private String status;
Category class...

@Id
@GeneratedValue
@Column(name = "CATEGORY_ID")
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "PARENT_ID")
private Category parent;

@OneToMany(mappedBy = "parent")
private List<Category> child = new ArrayList<>();

@ManyToMany
@JoinTable(name = "CATEGORY_ITEM",
joinColumns = @JoinColumn(name = "CATEGORY_ID"),
inverseJoinColumns = @JoinColumn(name = "ITEM_ID"))
private List<Item> items = new ArrayList<>();

0개의 댓글