객체 관계 매핑(ORM)에서 가장 어려운 부분이 객체 연관관계와 테이블 연관관계를 매핑
하는 일이다.
연관관계를 매핑할 때 다음 3가지를 고려해야 한다.
- 방향(Direction)
- [단방향, 양방향] 존재
- 회원, 팀 관계가 있을 때
- 회원 → 팀 또는 팀 → 회원 둘 중 한 쪽만 참조한다면 단방향
- 회원 → 팀, 팀→ 회원 양쪽 모두 서로 참조하는 것은 양방향 관계
- 방향은 객체관계에만 존재하고, 테이블은 항상 양방향
- 다중성(multiplicity)
- [다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M)] 존재
- 회원, 팀 관계가 있을 때
- 여러 회원은 한팀에 속하므로 회원 : 팀 = N : 1
- 한팀에 여러 회원에 속하므로 팀 : 회원 = 1 : N
- 연관관계의 주인(owner)
- 객체를 양방향 연관관계로 만들면 연관관계의 주인을 정해야 한다.
단방향 연관관계
객체 연관관계와 테이블 연관관계의 가장 큰 차이
참조를 통한 연관관계는 언제나 단방향이다. 객체간에 연관관계를 양방향으로 만들고 싶으면 반대쪽에도 필드를 추가해서 참조를 보관해야 한다. 결국 연관관계를 하나 더 만들어야 한다. 하지만 이것은 엄밀히 말하면 양방향 관계가 아니라 서로 다른 단방향 관계 2개다. 반면에 테이블은 외래 키 하나로 양방향으로 조인할 수 있다.
단방향 관계(A->B)
class A{
B b;
}
class B {}
양방향 관계(A<->B)
class A {
B b;
}
class B {
A a;
}
객체 연관관계 vs 테이블 연관관계 정리
- 객체는 참조(주소)로 연관관계를 맺는다.
- 테이블은 외래 키로 연관관계를 맺는다.
- 참조를 사용하는 객체의 연관관계는 단방향이다.
- A → B (a.b)
- 외래 키를 사용하는 테이블의 연관관계는 양방향이다.
- A JOIN B , B JOIN A 둘다 가능하며 결과값도 같다.
객체 관계 매핑하기
테이블의 구조가 위와 같을 때, JPA를 사용해서 둘의 관계를 매핑해보자.
회원 엔티티
@Entity
@Getter
@Setter
public class Member {
@Id
@Column(name = "MEMBER_ID")
private String id;
private String username;
// 연관 관계 맵핑
@ManyToOne
@JoinColumn(name="TEAM_ID")
private Team team;
}
팀 엔티티
@Entity
@Getter
@Setter
public class Team {
@Id
@Column(name = "TEAM_ID")
private String id;
private String name;
}
회원 엔티티는 팀 엔티티에 N:1(단방향) 관계를 갖는다.
@JoinColumn
@JoinColumn은 외래키를 매핑할 때 사용한다.
속성 | 기능 | 기본값 |
---|---|---|
name | 매핑할 외래 키 이름 | 필드명 + _ + 참조하는 테이블의 기본키 컬럼명 |
referencedColumnName | 외래키가 참조하는 대상 테이블의 컬럼명 | 참조하는 테이블의 기본키 컬럼명 |
참고
@ManyToOne
private Team team;
만약 위와 같이 @JoinColumn을 생략한다면 외래 키를 찾을 때 기본 전략을 사용한다.
- 기본 전략 : 필드명 + _ + 참조하는 테이블의 컬럼명
- 필드명(team) + _ + 참조하는 테이블의 컬럼명(TEAM_ID)
즉, team_TEAM_ID 외래 키를 사용한다.
@ManyToOne
다대일 관계에서 사용한다.
속성 | 기능 | 기본값 |
optional | false로 설정하면 연관된 엔티티가 항상 있어야 한다. | true |
fetch | 글로벌 페치전략 | @ManyToOne=FetchType.EAGER, @OneToMany=FetchType.LAZY |
cascade | 연속성 전이 기능 |
양방향 연관관계
지금까지 알아본것은 회원에서 팀으로만 접근하는 다대일 단방향 매핑이었다.
이번에는 반대 방향인 팀에서 회원으로 접근하는 관계를 추가하여 회원 → 팀, 팀 → 회원으로 접근할 수 있도록 양방향 연관관계 매핑을 해보자
팀 엔티티에 회원 엔티티를 추가하자
팀 엔티티
@Entity
@Getter
@Setter
public class Team {
@Id
@Column(name = "TEAM_ID")
private String id;
private String name;
// 연관 관계 추가
@OneToMany(mappedBy = "team")
private List<Member> memberList = new ArrayList<>();
}
팀 엔티티에 컬렉션인 List<Member>를 추가하였고, @OneToMany 매핑 정보에 mappedBy 속성을 줬다.
mappedBy 속성은 양방향 매핑일 때만 사용하는데, 반대쪽 매핑의 필드 값을 주면된다. (Member 클래스의 Team 매핑 필드값인 team)
연관관계의 주인
@OneToMany의 mappedBy는 왜 필요한 것일까?
사실 객체에는 양방향 연관관계라는 것이 없고, 서로 다른 단방향 연관관계 2개를 묶어서 양방향인 것처럼 보이게 하는 것이다. (반면에 데이터베이스는 외래 키 하나로 양쪽이 서로 조인할 수 있음. 따라서 외래 키 하나만으로 양방향 연관관계를 맺음)
객체 연관관계는 아래와 같다
- 회원 → 팀 연관관계 1개(단방향)
- 팀 → 회원 연관관계 1개(단방향)
테이블 연관관계
- 회원 ←> 팀 연관관계 1개(양방향)
엔티티를 단방향으로 매핑하면 참조를 하나만 사용하므로 이 참조로 외래키를 관리하면된다.
그런데 엔티티를 양방향으로 매핑하면 회원 → 팀, 팀 → 회원 두곳에서 서로를 참조한다. 따라서 객체의 연관관계를 관리하는 포인트가 2곳으로 늘어난다.
엔티티를 양방향 연관관계로 설정하면 객체의 참조는 둘인데 외래 키는 하나다. 따라서 둘 사이에 차이가 발생한다. (어디서 외래 키를 관리해야 할까?)
이런 차이로 인해 JPA에서는 두 객체 연관관계 중 하나를 정해서 테이블의 외래키를 관리해야 하는데 이것을 연관관계의 주인(Owner)라고 한다.
양방향 매핑의 규칙 : 연관관계의 주인
양방향 매핑 시에는 두 연관관계 중 하나를 연관관계의 주인으로 정해야한다.
연관관계 주인만이 데이터베이스 연관관계와 매핑되고 외래 키를 관리(등록, 수정, 삭제)할 수 있다. 주인이 아닌 쪽은 읽기만 할 수 있다.
- 주인은 mappedBy를 사용하지 않는다.
- 주인이 아니면 mappedBy를 사용하여 주인을 지정해야 한다.
=> 연관관계의 주인은 외래 키가 있는 곳으로 설정해야 한다. 즉 위의 예에서 보면 외래키 team_id를 갖고있는 member테이블 즉, Member엔티티를 주인으로 설정해야 한다.
// 아래와 같은 코드는 무시됨(연관관계의 주인이 아니기 때문)
team1.getMemberList.add(member1);
team1.getMemberList.add(member2);
// 아래는 연관관계의 주인이 설정하는 것으로 적용됨
member1.setTeam(team1);
member2.setTeam(team2);
사실은 객체 관점에서 양쪽 방향에 모두 값을 입력해주는 것이 가장 안전하다. 이 부분은 편의 메소드를 만들어 두 객체의 관계를 저장하거나 삭제할 수 있다.
'프로그래밍 노트 > JPA' 카테고리의 다른 글
[JPA] 다양한 연관관계_일대다(1:N) (2) | 2019.10.27 |
---|---|
[JPA] 다양한 연관관계_다대일(N:1) (0) | 2019.10.27 |
[JPA] 제네릭(generic)한 컨버터(converter) 만들기. Generic Json Converter (0) | 2019.10.10 |
[JPA] 영속성관리_3 (플러시, 준영속) (0) | 2019.09.18 |
[JPA] 영속성관리_2 (영속성 컨텍스트 특징, 조회, 수정, 삭제) (0) | 2019.09.16 |