이 글은 2021년 5월에 작성되었으며 블로그를 이전하며 옮기게 되었습니다.
시작하기 앞서 저의 글에 대한 피드백이나 지적은 언제나 환영입니다 😊
연관관계 매핑
용어를 먼저 이해해보자
- 방향(Direction) : 단방향, 양방향
- 다중성(Multiplicity) : 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M) 이해
- 연관관계의 주인(Owner) : 객체 양방향 연관관계는 관리 주인이 필요하다.
연관관계는 왜 필요해?
"객체지향 설계의 목표는 자율적인 객체들의 협력 공동체를 만드는 것이다."
-조영호(객체지향의 사실과 오해)
: 객체를 테이블에 맞추어 데이터 중심으로 모델링하면, 협력관계를 만들 수 없다.
- 테이블은 외래키로 조인을 사용하여 연관된 테이블을 찾는다.
- 객체는 참조를 사용해서 연관된 객체를 찾는다.
- 테이블과 객체 사이에는 이런 큰 차이가 있다.
즉, 제대로된 협력 공동체를 만들기 위하여 연관관계가 필요하다
연관관계 매핑 시 고려사항 3가지
- 다중성
- 단방향, 양방향
- 연관관계의 주인
다중성
- 다대일(N:1) :
@ManyToOne
- 일대다(1:N) :
@OneToMany
- 일대일(1:1) :
@OneToOne
- 다대다(N:M) :
@ManyToMany
모두 대칭성이 있다.
테이블과 객체의 연관관계 차이
테이블
- 외래키 하나로 양쪽 조인 가능
- 사실 방향이라는 개념이 없다 객체
- 참조용 필드가 있는 쪽으로만 참조 가능
- 한쪽만 참조하면 단방향
- 양쪽이 서로 참조하면 양방향 (사실 단방향이 2개)
단방향 연관관계
@ManyToOne
ex) 하나의 팀이 여러 명의 멤버를 가질 수 있음@JoinColumn(name = "TEAM_ID)
ex) Join 하는 컬럼은 TEAM_ID 즉 Team을 TEAM_ID로 Join
양방향 연관관계
단방향 -> Member에서 Team으로 접근 가능하지만 Team에서 Member로 접근 불가하다
하지만 참조(reference)를 넣어두면 양쪽 다 참조해서 접근할 수 있게 된다.
양방향 연관관계를 만들어도 단방향과 비교해봐도 테이블에 변화가 없다 (테이블은 결국 다 Join으로 조회할 수 있기 때문이다) 테이블의 연관관계는 외래 키 하나로 양방향이 다 있는 것이다.
이때 객체에선 문제가 일어난다 Member에선 Team으로 갈 수 있지만 Team에선 Member에 접근할 수 있는 방법이 없었다.(객체 참조와 테이블의 외래 키의 가장 큰 차이이다)
그래서 Team에 List형식의 memebers를 넣어주면 양쪽으로 다 접근할 수 있게 된다.
- JPA를 설계할 때 양방향 매핑은 하지 말고 단방향 매핑만 해놓자 (처음엔 단방향 매핑으로 설계를 끝내야 한다)
- JPQL에서 역방향으로 탐색할 일이 더 많다
- 단방향 매핑을 잘해놓고 양방향은 필요할 때 추가해도 됨 (테이블에 영향을 주지 않는다)
연관관계의 주인
- 객체의 두 관계 중 하나를 연관관계의 주인으로 지정한다.
- 연관관계의 주인만이 외래 키를 관리한다. (등록, 수정)
- 연관관계의 주인이 아닌 쪽(주인의 반대편)은 읽기(단순 조회)만 가능하다. (외래 키에 영향을 주지 않음)
- 주인은
mappedBy
속성을 사용하지 않는다. - 주인이 아니면
mappedBy
속성으로 주인을 지정해준다
ex) List members가 Team team을mappedBy = "team"
으로 team을 주인으로 지정하고 주인인 team이 외래 키를 관리한다.
그래서 누구를 주인으로 해야 하는데?
Member.team
이 연관관계의 주인 (진짜 매핑)이다.Team.members
는 주인의 반대편(가짜 매핑)
외래 키가 있는 곳을 주인으로 정하자
그렇지 않으면 단점이 있다
- 일단 헷갈린다
- Team.members의 값을 바꿨다고 가정해보자 그럴 시 다른 테이블에 update쿼리문이 나가게 된다
(외래 키 관련으로 (Team에서 insert쿼리, Member에서 update쿼리 등이 나가고 복잡해져 버린다.)) - 위와 같은 이유들로 성능 이슈 또한 날 수 있다.
외래 키가 있는곳을 주인으로 하는 규칙으로 하자!
이렇게 되면 설계가 깔끔해지며 외래키가 있는 테이블에서 관리가 된다는 장점이 있다.
- DB입장에서 보면 외래 키가 있는곳이 무조건 '다'( * , N )이다.
- 외래키가 없는 곳이 무조건 ( 1 )이 되는 것
즉 DB의 N 쪽이 무조건 연관관계의 주인이 된다
ex)@ManyToOne
연관관계의 주인을 정하는 기준
- 비즈니스 로직을 기준으로 연관관계의 주인을 선택하면 안 됨
- 연관관계의 주인은 외래 키의 위치를 기준으로 정해야 함
예를 들자면 자동차와 자동차의 바퀴가 있다.
비즈니스적으로 보면 자동차가 훨씬 중요하지만 연관관계의 주인을 바퀴로 잡는 격인 것이다.
정말 간단하게 말해서 연관관계의 주인은 그냥 N 쪽 '다'인 쪽을 연관관계의 주인으로 설정하면 된다.
양방향 연관관계 매핑 시 가장 많이 하는 실수
- 연관관계 주인에 값을 입력하지 않는 것!
- 실무에서 조회만 가능한 즉, 주인이 아닌 쪽(가짜 매핑)에 값을 넣고 DB에 값이 안 들어가는 사고가 난 사례가 있음*
ex) members 즉, 연관관계의 주인이 아닌 곳 가짜 매핑에 입력하고 있었던 것!
그래서 Member에 TEAM_ID가 null값이 들어가는 것이다. - 연관관계 주인에서 값을 지정해주자*
- 양방향 매핑 시 무한루프를 조심하자
ex)toString()
,lombok
,JSON생성 라이브러리
toString()
,lombok
:toString()
을 양쪽에서 출력하여 스택오버플로우 오류가 일어난다.JSON생성 라이브러리
: 쭉 다 뽑아버리기 때문에 엔티티를 JSON으로 바꾸는 순간 무한루프 -> 스택오버플로우(장애) 컨트롤러에서 엔티티를 바로 반환할 때 엔티티를 json으로 변환하며 연관관계가 양방향으로 걸려있다면 그때 계속 서로 파고들면서 장애가 남
오류 해결
toString
: 써도 매핑된 것들을 빼고 써라json생성 라이브러리
: 컨트롤러에서 절대 엔티티를 반환하지 마라 (DTO 사용하기)
사실 양방향 연관관계 매핑에는 정답이 있다.
바로 순수한 객체 관계를 고려하여 항상 양쪽 다 값을 넣어주는 것이다.
- 순수 객체 상태를 고려해서 항상 양쪽에 값을 설정하자
- 연관관계 편의 메서드를 이용하자
: 연관관계 편의 메서드를 사용 시 어느 쪽에 값을 넣어줄지는 개발자 마음대로이다.
연관관계 편의 메서드 예시
//연관관계 주인인쪽(Member)에서 작성
public void changeTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
//연관관계 주인이 아닌쪽(Team)에서 작성
public void addMembers(Member member) {
member.setTeam(this);
members.add(member);
}
글을 마치며
JPA에서 중요한 다른 한 가지 연관관계 매핑에 대해 정리해보았습니다.
JPA설계 등 에서 매우 중요하다고 생각되었으며
다음 글은 다중성을 이용한 다양한 연관관계 매핑입니다!
긴 글 읽어주셔서 감사합니다.😊
'JPA' 카테고리의 다른 글
JPA 값타입 (0) | 2022.03.25 |
---|---|
JPA 프록시와 연관관계 관리 (0) | 2022.03.25 |
JPA 상속관계 매핑과 고급매핑 (0) | 2022.03.25 |
JPA 다양한 연관관계 매핑 (0) | 2022.03.25 |
JPA 영속성 컨텍스트란? (0) | 2022.03.25 |
댓글