본문 바로가기

JPA

1월 19일 JPA 양방향 연관관계와 연관관계의 주인

양방향 연관관계와 연관관계의 주인

 

객체간 참조와 테이블간 JOIN의 차이점을 이해

 

양방향 (객체) 연관관계

{Member}(N)

-id(pk)

-Team team (fk)

-username

 

{Team}(1)

-id(pk)

-name

-List Member(fk) //양방향 연관관계일 경우 반대편에서도 조회할수 있도록 필드이 추가된다.

 

양방향 (테이블) 연관관계

{Member}

-Member_ID(pk)

-Team_ID(fk)

-username

 

{Team}

-Team_ID(pk)

-name

// 테이블에서는 따로 컬럼이 추가되지 않는다.

 

단방향은 Member에서 Team을 조회하는건 가능했지만 그 반대로 Team에서 Member로 가지는 못했다.

이걸 가능하게 한것이 양방향 으로

테이블 연관관계는 단방향 , 양방향 모두 동일하지만

객체 연관관계에서는 team에서 member의 fk를 가지도록 추가된다.

 

DB에서 Member가 자신이 속한 Team이 알고 싶다면

양측의 Team_ID로 Join 하면 되는데

member join team , Member에서 Team을 찾을 경우 

team join member , Team 에서 Member를 찾을 경우

테이블은 단방향의 개념이 없다. PK , FK 로 다룰 수 있기때문이다.

 

문제는

JPA객체 상에서는 Member만 Team 객체를 가지고 있어 양방향으로 탐색이 불가능하다는것인데

그래서 team객체쪽에도 member객체를 넣어준다.

 

양방향 매핑

@Entity

public class Team

@Id , @GenerateValue

private Long id;

private String name;

@One to Many (mappedBy = "team")

List<member> members = new ArratList<member>();

//team 입장에선 일대 다 연관관계이기 때문에 member를 List로 받아온다.

 

@Entity

public class Member

@Id @GeneratedValue

@Column(name = "member_id")

private Long id;

@Column(name = "username")

private String username;

@Many to One

@JoinColumn(name = "Team_Id")

private Team team

 

Member에서 ManyToOne 이니 Team에서는 OneToMany로 받아야 한다.

이때 옵션으로 mappedBy가 오게되는데 이는 member에 "team"필드를 참조한다는 뜻으로

이러면 Team에서 member가 조회가능하다

 

Member findMember = em.find(Member.class , Member.getId());

1.Member값을 가져온다

List<member> members = findMember.getTeam().getMembers();

2.찾아온 member에서 필드 Tram을 가져오고 그 Team에 Member필드를 가져온다

(멤버가 속한 팀을 찾고 , 그 팀에서 팀에속한 맴버들을 모두 찾는다.)

 

값을 가져올수는 있는데 넣어주지도 않았는데 어떻게 가져오는걸까

이걸 이해하려면 mappedBy옵션에 대해서 알아야한다.

객체와 테이블간에 연관관계를 맺는 차이를 이해해야한다

 

객체와 테이블이 관계를 맺는 차이점

객체에선 연관관계가 2개 필요하다 단방향을 서로 가지고 있어서 연결하는것이다.

member > team 연관관계 1개(단방향)

team > member 연관관계 1개(단방향)

단방향을 서로 만들어 억지로 양방향을 형성하는것

 

테이블 연관관계 1개

member <-> team 의 연관관계 1개(역방향)

PK 과 FK 로 서로 연결하는것.

 

객체의 양방향 관계

객체의 양방향 관계는 사실 양방향 관계가 아니라 

서로 다른 양방향 관계 2개의 관계이다.

 

객체를 양방향으로 참조하려면 단방향 연관관계를 2개 반들어야한다.

A > B (a.getB()) 

B > A (b.getA())

 

테이블의 양방향 연관관계

테이블은 외래키 하나로 두 테이블의연관관계를 관리한다

member에 , Team_ID 외래키 하나로 양방향 연관관계를 가진다.

(외래키 하나로 양쪽으로 조인을 할 수 있다)

 

select*

from Member m 

Join Team T on M.team_ID = T.team_ID

 

select *

from Team T

Join Member M on T.Team_ID = M.Team_ID

 

그럼 객체에 두 단방향 필드 Team team 과 List<Member>중 어느게 변경되었을때 DB에 fk인 Team_ID를 수정해야 할까

그래서 두 단방향중 어느 하나를 주인으로 마들어 KF와 견결시켜 주어야 한다.

 

연관관계의 주인(Orner)

양방향 매핑 규칙

-객체의 구 관계중 하나의 연관관계를 주인으로 지정

-연관 관계의 주인만이 외래키를 관리(등록 , 수정)

-주인 아닌쪽은 수정이 불가능하고 읽기만 가능하다

-주인은 mappedBy 속성 사용금지

-주인이 아니면 mappedBy속성으로 주인쪽 필드를 지정

 

둘중 주인으로 정해줄 대상은 대게 정해져 있다

우선 member에 team은 @MantToOne 과 @JoinColumn(name = "team_id")로

DB에 fk와 실질적으로 연결되어 있고

team의 member는 @mappedBy(name="team")으로 member의 team에 연결되어 있다

 

@mappedBy가 선언된 필드에는 값을 조회밖에 할수없게된다.

FK에 값을 변결할때는 member에 team만을 참조하게 된다.

 

그래서 누구를 주인으로?

실질적으로 DB에 FK와 연결된 쪽을 주인으로 정해준다

외래키FK를 가진쪽이 무조건 (N) , 외래키가 없는곳이 무조건(1) 이다.

Many쪽이 무조건 주인

 

Members는 곧 member들의 team값, 

members = member1 , member2 , member3

 

members는 Many에 해당해는 객체의 mappedBy된 값들을 모두 가져오는 역활

team의 members는 member가 가진 team에 fk를 가져오고

member의 team은 team이 가진 fk를 추출

 

Member는 team값을 하나 가진다

여러 Member 들이 모두 Team 값을 하나씩 가진다.

그럼 team은 mappedBy(name = "team") 과 @OneToMany에 의해

모든 Member 가 가진 Team값을  모두 가지게 되는데

여기서 member에 team값을 모두 가지게 된다는것은

team필드가 @OneToMany(mappedBy = "team") 에 의해

team필드를 가진 member값이 members에 들어오게 된다.

 

이는 member에서 Team_ID대신 @ManyToOne으로 Team객체를 받아와 거기서 FK값만 추출한 것과 같다.

 

@ManyToOne 이나 @OneToMany는 객체값을 가져와서 그 안에 FK값만을 가지고 있을수 있게 해준다.

mappedBy에 의해 team 필드를 가진 member를 member객체 상대로 모두 가지고 있게 된다.

 

주인 객체와 주인이 아닌객체를 연결해 주어야 하는데

엔티티의 생성자 메소드를 이용한다.

 

예를들어

@ManyToOne

@JoinColumn("order")

private order order;

위와 같은 필드를 가진 orderItem이 객체가 있다고 할때

 

public static orderItem createOrderItem (Item item , int orderPrice , int count){

 

OrderItem orderItem = new OrderItem();

orderItem.setItem(item);

orderItem.setOrderPrice(orderPrice);

orderItem.setCount(count);

 

item.removeStock(count);

return orderItem;

}

 

위와 같은 생성자 함수가 있다고 할때

order필드를 바로 넣어주지 않는다.

orderItem이 Many쪽이고 order이 one 쪽이니 one에 넣어줄대 orderItem쪽에도 충족되도록 설계한다.

 

그래도 order쪽의 생성자를 보면

@OnetoMany(mappedBy="order" , cascade=cascade=type.ALL)

private List<orderItem> orderItems = new ArrayList<>();.

 

public static Order createOrder(Member member , Delivery delivery , OrderItems... orderItems){

 Order order = new Order();

 order.setMember(member);

 order.setDelivery(delivery);

 

 for(OrderItem orderItem : orderItems){

  order.addOrderItem(orderItem)

 }

 

order.setStatus(orderStatus.ORDER)

order.setOrderDate(localDateTiem.now())

 

}

 

public void addOrderItem(OrderItem orderItem){

 orderItems.add(orderItem);

 orderItem.setOrder(this);

}

 

Order는 위와같은 형태를 하고 있는데 중요한건 addOrderItem 이다

addOrderItem 의 로직을 보면 일단 order에 orderItems필드에  orderItem을 추가하고 

그 orderItem에 현재 order(this)를 추가해줌으로서 

주인과 주인이 아닌 객체간에 연관관계가 형성된다.