연관관계는 왜 필요한가?
- 객체를 테이블에 맞추어 모델링(외래키를 field로 설정 → ex) teamId) 하면 자유자재로 객체 그래프를 이용할 수 없음! → 객체 지향적 설계 불가능
- 즉, 협력 관계를 만들 수 없음.
- 테이블과 객체 사이에는 이런 큰 간격이 존재
- 이 간격을 JPA는 연관관계로 해결하고자 하는 것!
연관관계 (단방향, 양방향)
단방향 연관관계
- 객체 지향 모델링
- 객체를 테이블에 맞추어 모델링하는 것이 아닌, 테이블에 초점되지 않고 객체가 협력관계를 이용할 수 있도록 모델링하는 것 → 객체 연관관계 사용
- 즉, 외래키값을 그대로 갖는 것이 아닌, 해당 연관관계를 가지고 있는 객체 자체를 참조(Reference)로 가질 수 있도록 설정하는 것
- ex)
private Long teamId;
→private Team team;
: 객체 그래프 탐색이 가능해짐
- 단방향 연관관계
- Entity 의 입장. Table의 입장이 아님
- 두 객체의 연관관계가 있을 때, 객체의 이동이 한 객체에서밖에 하지 못하는 경우
- 즉, 참조(Reference)가 한곳에만 있는 것.
- [주의] Table에서는 단방향은 존재하지 않음. → Join으로 양방향으로 이동 가능!
-
연관관계 예시 (단방향)
- 참조가 Member에만 있음 → 단방향
-
Member Class 참조 부분
// Class Member @ManyToOne @JoinColumn(name = "TEAM_ID") private Team team;
@ManyToOne
- 다대일(N:1)
- 참조된 객체(Reference)와 어떤 관계인지를 알려주는 것
- 즉, DB 입장에서 현재 Class의 Entity와 Referecne된 Entity가 어떤 연관관계인지를 알려주는 것 (DB에는 연관관계라는 개념이 없으므로!)
-
Member 입장 : Many Team 입장 : One ⇒ ManyToOne
-
@JoinColumn(name = "TEAM_ID")
- Member Entity 의 team(Reference)과 Member Table 의 team_id(foreign key)를 매핑한다는 것
- 연관관계의 주인이 갖는 어노태이션 (↔
mappedBy
)
- 즉, 두 객체의 관계가 무엇인지(일대다, 다대일, …), 그리고 이 객체를 Table 의 입장에선 어떤 Column 과 매핑할 것인지를 설정만 해주면 이를 통해 연관관계를 설정하고 결과적으로 객체 지향적인 설계가 가능함!
- 결국 참조를 사용하여 연관된 객체를 찾을 수 있음 → 협력관계 설정 가능
- 객체 지향적인 설계(사용)를 위해 DB Table 입장에서의 상황을 알려주는 것
-
이렇게 연관관계를 설정하면
Member member = em.find(Member.class, 1L); Team team = em.find(Team.class, member.getTeamId());
이렇게 할 필요 없이 바로 참조(협력관계)를 이용하여
Member member = em.find(Member.class, 1L); Team team = member.getTeam();
로 해당 연관관계의 객체를 접근할 수 있음! → 객체 지향적인 설계
양방향 연관관계와 연관관계의 주인
- 양방향 연관관계
- Entity 의 입장. Table의 입장이 아님
- 두 객체의 연관관계가 있을 때, 객체의 이동이 두 객체 모두에서 할 수 있는 경우
- 즉, 참조(Reference)가 양쪽에 있는 것. (양쪽으로 참조해서 갈 수 있는 것)
- 사실 양방향은 단방향이 두개로 되어 있는 것을 말하는 것
- [주의] Table에서는 항상 양방향이 가능! 즉, Table은 단뱡향이든 양방향이든 Table은 전혀 변화가 없음 (Join이 존재하기 때문! → 방향이란 개념이 없음)
-
양방향 연관관계 예시
- 참조가 Member, Team 둘 다에 있음 → 양방향
- 중요한 건 단방향이든 양방향이든 Table에 변화는 없음! → Table에는 방향이라는 것이 없음 (즉, 당방향이든 양방향이든 모든 가능!)
-
Team Class 참조 부분 (Member Class 는 단방향에서와 동일)
@OneToMany(mappedBy = "team") private List<Member> members = new ArrayList<>();
@OneToMany(mappedBy = "team")
- 해당 객체와 어떤 관계인지를 알려주는 것 (DB입장에서 어떤 관계인지 알려주는 것)
-
Team 입장 : One Member 입장 : Many ⇒ OneToMany mappedBy = "team"
: MEMBER 객체의 어떤 Field 와 연관이 있는가 (DB 의 입장은 아님. DB 는 단방향이든 양방향이든 연결 가능! 객체 지향적인 설계를 위한 설정임!)
- 이렇게 되면 Team에서도 연관된 Member들을 List로 확인이 가능함 → 객체지향적인 설계(객체그래프탐색 가능)
- 하지만, 양방향이 항상 좋은 것은 아님. 객체는 단방향이어야 객체그래프 자체가 순환되지 않고 좋음 → 필수적인 상황(역추적)에서만 사용하는 것이 좋음
- 연관관계의 주인
- 객체와 테이블간에 연관관계를 맺는 차이를 이해해야 됨
- 차이
- 객체의 연관관계 = 2개 (양방향)
- 사실 양방향 관계가 아니라 서로 다른 방향의 단뱡향 관계 2개
- A → B 1개 (단방향)
- B → A 1개 (단방향)
- 테이블 연관관계 = 1개 (양방향)
- 테이블은 외래 키 하나로 두 테이블의 연관관계를 관리
- A ↔ B 1개 (양방향)
- 객체의 연관관계 = 2개 (양방향)
mappedBy
- 객체의 양방향 관계는 사실 양방향 관계가 아니라 서로 다른 방향의 단뱡향 관계 2개!
- 즉, 객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어 줘야 함!
- 반면에, 테이블은 외래 키 하나로 두 테이블의 연관관계를 관리하고 있음 (외래 키 하나로 A→B 로, B→A로 갈 수 있음)
- 즉, 외래 키 하나로 양방향 연관관계를 가짐! (양 쪽으로 JOIN 가능!)
-
여기서 딜레마 발생! → A에서 B를 바꿨을 때 Table 에 반영(외래키 변경)을 해야 되는지, B에서 A가 변경되었을 때 Table 에 반영(외래키 변경)해야 될 지 딜레마에 빠지게 됨 (객체로 보면 A,B 둘 다에 참조값이 있지만 Table 에는 A에만 FK 가 있으니!)
예를 들어 Member에서 Team 을 바꿨을 때 Member의 변경된 Team으로 Member Table의 외래 키를 바꿔야 할지, Team에서 members의 Member가 변경되었을 때 변경된 Team으로 Member Table의 외래 키를 변경해야될지 딜레마에 빠지게 되는것.
- 딜레마 해결! → 둘 중에 하나로 외래 키를 관리하자! ⇒ “연관관계의 주인” → 외래 키가 있는 놈으로 주인을 설정하자! (다대일 관계에서 “다“에 해당하는 객체!)
- 즉,
mappedBy
는 관계 상의 ‘을’을 지칭하는 것 (변경 권한이 없는 것!) - [참고] 관계 상의 ‘을’은 비지니스 로직에서의 중요도를 말하는 것이 아닌 오직 관계에 있어서의 권한을 이야기하는 것
- 연관관계 주인 설정
- 객체의 두 관계 중 하나를 연관관계의 주인으로 지정 (Table FK는 한 Table에만 존재하므로)
- 연관관계의 주인만이 외래 키를 관리 (등록, 수정)
- 주인이 아닌쪽(
mappedBy
)은 읽기만 가능 - 주인은 mappedBy 속성 사용 X
- 주인이 아니면 mappedBy 속성으로 주인이 아닌 것을 표현
- 그럼 누가 연관관계의 주인??
- 외래 키(FK)가 있는 곳 (정해진 것은 없지만 권장하는 것)
- Table 의 입장에서 외래키가 있는 곳은 다대일(N:1)관계에서 “다”
- 즉, “다” 쪽이 연관관계의 주인으로 설정하는 것이 좋음
- 만약 외래 키가 있지 않은 반대 편의 Table을 연관관계의 주인공으로 설정한다면, 수정 시 다른 Table에 update query가 나가는 일이 발생!! → 너무 복잡해짐 ex) A에 외래키가 있는데, B가 연관관계의 주인이 된다면, B의 A값이 변경된다고 할 때 B Table에 update query가 나가는 것이 아닌 A Table에 UPDATE query가 나감. 즉, B를 수정했는데 B Table이 아닌 A Table이 수정되는 것!! → 복잡해~ 헷갈려~
- [주의] 연관관계의 주인이라고 비지니스적으로 우위에 있는 것이 아닌 그냥 관계상의 주인
- 연관관계의 주인에서 주의해야할 것들
- 연관관계의 주인에 값을 입력하지 않는 경우
-
문제
Member member = new Member(); member.setName("park"); // member.setTeam(team) 을 까먹은 것 team.getMembers().add(member); // 역방향(주인X)만 연관관계 설정
-
해결
-
연관관계 편의 매서드를 따로 지정하자!
public void changeTeam(Team team) { // changeTeam this.team = team; team.getMembers().add(this); }
- 연관관계의 주인을 기준으로 이와 연관된 객체까지 놓치지 않도록 메서드를 만들어주는 것
- member의 team을 변경하거나 설정할 때, 항상 역방향의 객체에도 해당 사항을 적용해주는 것!
-
-
- 양방향 매핑시의 무한루프
- 문제
toString()
, Lombok , JSON 생성 라이브러리 사용 시 해당 되는 모든 값들을 가져와 적용하기 때문에 양방향 매핑을 해두었다면 계속해서 순환되는 무한루프에 빠지게 됨- ex) Member.getTeam() → Team.getMembers() → 해당 결과(Member List)의 각 Member.getTeam() → 해당 결과 객체(Team)의 Team.getMembers() → …
- 해결
toString()
이나 Lombok 사용 시 해당 양방향을 반환하지 않도록 따로 설정 필요!- JSON 생성 라이브러리 같은 경우 Conroller의 return 반환 값으로 자주 사용 되는데, 그 때 Entity를 직접 반환하지 말고 양방향관계의 객체를 제외한 DTO를 사용하여 반환해라!
- 문제
- 연관관계의 주인에 값을 입력하지 않는 경우