연관관계 매핑 기초
- 객체지향스럽게 만드는 방법의 기본이자 어려운 부분
- 객체와 테이블의 연관관계의 차이를 이해해야한다.
- 객체를 테이블에 맞춰 데이터 중심으로 모델링하면 , 협력관계를 만들 수 없다.
왜냐하면, 테이블은 외래 키로 조인을 사용해서 연관된 테이블을 찾고 객체는 참조를 사용해서 연관된 객체를 찾는다.
따라서, 테이블과 객체 사이에는 큰 간격이 존재.
객체 지향 모델링
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
양방향 연관관계와 연관관계의 주인
- 테이블에 연관관계에서는 방향이 없다. 그냥 외래키와 조인만 있으면 된다.
하지만, 객체가 중요하다 반대편에 어떤 것과 관계 매핑되어 있는지 알려줘야 한다.
@OneToMany(mappedBy = "team")
List<Member> members = new ArrayList<Member>();
가급적이면 단방향이 고려사항이 적어 더 좋다.
객체와 테이블이 관계를 맺는 차이점
- 객체 연관관계 = 2개
회원 -> 팀 연관관계 1개 (단방향)
팀 -> 회원 연관관계 1개 (단방향)
- 테이블 연관관계 = 1개
회원<->팀 의 연관관계 1개 (양방향)
객체의 양방향 관계
- 객체의 양방향 관계는 사실 양방향 관계가 아니라 서로 다른 단방향 관계 2개
결국, 객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야 한다.
테이블의 양방향 연관관계
- 테이블은 외래 키 하나로 두 테이블의 연관관계 관리 하고 조인하여 참조 한다.
여기서 오는 딜레마, 어떤 객체로 테이블의 외래키를 관리 해야하나?
- Member와 Team 객체 둘다 연관관계 매핑을 해놓았기 때문에 둘 다 관리가 가능은 하지만
둘 중 하나로만 외래키를 관리해야한다. 여기서 나오는 개념이 연관관계의 주인
연관관계의 주인(Owner)
양방향 매핑 규칙!!!
- 객체의 두 관계중 하나를 연관관계의 주인으로 지정
- 연관관계의 주인만이 외래 키를 관리(등록, 수정)
- 주인이 아닌쪽은 읽기만 가능
- 주인은 mappedBy 속성 사용X
- 주인이 아니면 mappedBy 속성으로 주인 지정
어떤 것을 주인으로 하는 것이 좋을까?
- 외래키가 있는 곳을 주인으로 정하면 된다.
- Member.team가 연관관계 주인

양방향 매핑시 가장 많이 하는 실수
1.순수 객체 상태를 고려해서 항상 양쪽에 값을 설정하자
(양방향 매핑시 양쪽 다 값을 세팅해줘야한다.)
team.getMembers().add(member);
member.setTeam(team);
2.연관관계 편의 메소드를 생성하자
(위처럼 양쪽 값 세팅을 둘 다 하는게 깜빡 할 수 있기 때문에 메소드를 만들어 버린다.)
(반대로 Team쪽에도 연관관계 편의 메서드를 만들 수 있다. 정해서 한쪽에만 만드는 것이 좋다. )
class Member {
.
.
.
public void changeTeam(Team team){
this.team = team;
team.getMembers().add(this);
}
}
3.양방향 매핑시에 무한 루프를 조심하자
ex) toString(), lombok, JSON 생성 라이브러리 (서로를 매핑으로 참조하다보니까 왔다갔다 계속무한루프에 빠짐)
양방향 매핑에 대한 결론
- 단방향 매핑만으로도 이미 연관관계 매핑은 완료(양방향은 고민거리가 추가된다.)
- 양방향이 아니어도 다른방법으로 충분히 만들 수 있다. 실무에서는 JPQL때문에 양방향 조회가 필요할 때가 있어서 주로 용함.
- 양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색) 기능이 추가된 것 뿐
- JPQL에서 역방향으로 탐색할 일이 많음, 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도 됨 (테이블에 영향을 주지 않음)
연관관계의 주인을 정하는 기준
- 비즈니스 로직을 기준으로 연관관계 주인을 선택하지 않는다.
- 외래키를 가지고 있는 기준으로 정해야한다.
다양한 연관관계 매핑
연관관계 매핑시 고려사항 3가지
1. 다중성
- 다대일: @ManyToOne
- 일대다: @OneToMany
- 일대일: @OneToOne
- 다대다: @ManyToMany
2. 단방향, 양방향
- 테이블: 외래키 하나로 양쪽 조인가능 , 방향이라는 개념 x
- 객체 : 참조용 필드가 있는 쪽으로 참조 가능 , 한쪽만 참조는 단방향, 양쪽 참조시 양방향
3. 연관관계의 주인
- 객체는 참조가 2군데 되고 테이블은 외래키가 하나로 연관관계를 맺는데 이 때 노선 정리를 해주는 것을 의미
- 외래키가 있는 곳에 연관관계의 주인으로 설정
- 주인의 반대편은 외래키에 영향을 주지 않고 단순 조회만 가능
다대일 [N:1]
단방향 일 때
- 가장 많이 사용하는 연관관계
- 외래키가 있는 곳에 참조를 걸고 연관관계 매핑을 하면 된다.
양방향 일 때
- 외래 키가 있는 쪽이 연관관계의 주인
- 양쪽을 서로 참조하도록 개발
일대다 [1:N]
단방향 일 때
- 일대다 단방향은 일대다(1:N)에서 일(1)이 연관관계의 주인
- 테이블 일대다 관계는 항상 다(N) 쪽에 외래 키가 있음
- @JoinColumn을 꼭 사용해야 함. 그렇지 않으면 조인 테이블 방식을 사용헌디야(중간에 테이블을 하나 추가함)
- 객체와 테이블의 차이 때문에 반대편 테이블의 외래 키를 관리하는 특이한 구조
- 단점 : 엔티티가 관리하는 외래 키가 다른 테이블에 있고, 연관관계 관리를 위해 추가로 UPDATE SQL 실행
- 따라서, 일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하자
양방향에 대해서
- 이런 매핑은 공식적으로 존재X (가라임)
- 깔끔하게 그냥 다대일 양방향을 사용하자
일대일 [1:1]
- 일대일 관계는 그 반대도 일대일
- 주 테이블이나, 대상 테이블 중에 둘 중 아무거나 외래 키 선택 가능
- 외래 키에 데이터베이스 유니크(UNI) 제약조건 추가
주 테이블에 외래키를 매핑하는 방법, 단방향과 양방향 둘 다 다대일 관계와 유사하게 사용
- 다대일 양방향 매핑 처럼 외래 키가 있는 곳이 연관관계의 주인, 반대편은 mappedBy 적용
- 객체지향 개발자 선호
- 장점 : 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인 가능
- 단점 : 값이 없으면 외래 키에 null 허용(DB개발자들이 싫어함)
대상 테이블에 외래키 단방향
- 대상 테이블에 외래키 단방향은 JPA 지원X
- 양방향만 지원한다.
대상 테이블에 외래 키 양방향
- 전통적인 데이터베이스 개발자 선호
- 일대일 주 테이블에 외래 키 양방향과 매핑 방법은 같다. 그냥 주랑 대상만 바뀐 것.
- 장점: 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조 유지
- 단점: 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩됨
정리 하자면, 주 테이블에 외래 키를 매핑하는 것이 객체지향적으로나 JPA 단방향 매핑이 가능하여 편리하다.
너무 여기에 매몰되지 말고 그냥 상황에 따라 자연스럽게 받아들이고 설계해도 된다.
다대다 [N:M] 사용하지 않는다.
- 관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다.
- 연결 테이블을 추가해서 일대다, 다대일 관계로 풀어내야한다.
- 실무에서 사용X
다대다 관계 일 때?
- 정규화 하듯이 풀어준다.
- 연결 테이블용 엔티티 추가(연결 테이블을 엔티티로 승격)
- @ManyToMany -> @OneToMany, @ManyToOne
주요 연관관계 매핑 어노테이션
@JoinColumn (외래 키를 매핑할 때 사용) 속성
- name : 매핑할 외래키 이름 (기본값 = 필드명 + _ + 참조하는 테이블의 기본 키 컬럼명)
- referencedColumnName : 외래 키가 참조하는 대상 테이블의 컬럼명
(기본값 = 참조하는 테이블 기본키 컬럼명)
(개발하다보면 외래키의 참조하는 대상의 컬럼명이 달라질 수 있다 이 때 사용)
- foreignKey(DDL) : 외래 키 제약조건을 직접 지정(테이블을 생성할 때만 이 속성 사용)
- 나머지 @Column의 속성과 같다.
@ManyToOne의 주요 속성
- optional : false로 설정하면 연관된 엔티티가 항상 있어야 한다. (기본값 = TRUE)
- fetch : 글로벌 페치 전략을 설정한다. (기본값 = FetchType.EAGER )
- cascade : 영속성 전이 기능을 사용한다.
- targetEntity : 연관된 엔티티의 타입 정보를 설정한다. 이 기능은 거의 사용하지 않는다. 컬렉션을 사용해도 제네릭으로 타입 정보를 알 수 있다.
@OneToMany의 주요속성
- mappedBy : 연관관계의 주인 필드를 선택한다.
- fetch : 글로벌 페치 전략을 설정한다. (기본값 = FetchType.LAZY)
- cascade : 영속성 전이 기능을 사용한다.
- targetEntity : 연관된 엔티티의 타입 정보를 설정한다. 이 기능은 거의 사용하지 않는다. 컬렉션을 사용해도 제네릭으로 타입 정보를 알 수 있다.