- 객체와 테이블 연관관계의 차이를 이해
- 객체의 참조와 테이블의 외래 키를 매핑
- 엔티티의 연관관계를 매핑할 때 다음 3가지를 고려해야 한다.
- 다중성
- 단방향, 양방향
- 연관관계의 주인
1. 단방향 연관관계
- 객체 연관관계
- 회원 객체는 Member.team 멤버 변수로 팀 객체와 연관관계를 맺는다.
- 회원 객체와 팀 객체는 단방향 관계다.
- Member.team 필드를 통해서 팀을 알 수 있지만 반대로 팀은 회원을 알 수 없다.
- member.getTeam() 가능, team.getMember()는 불가능
- 테이블 연관관계
- 회원 테이블은 TEAM_ID 외래 키로 팀 테이블과 연관관계를 맺는다.
- 회원 테이블과 팀 테이블은 양방향 관계이다.
- 회원 테이블의 TEAM_ID 외래 키를 통해서 회원과 팀을 조인할 수 있고 반대로 팀과 회원도 조인할 수 있다.
- 객체 연관관계와 테이블 연관관계의 가장 큰 차이
- 참조를 통한 연관관계는 언제나 단뱡향이다.
- 양쪽에서 서로 참조하는 것을 양방향 연관관계라 한다.
- 정확히 이야기하면 양방향 관계가 아니라 서로 다른 단방향 관계 2개다.
(1) 객체 관계 매핑
@Entity
@Getter @Setter
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
}
@Entity
@Getter @Setter
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
}
- @ManyToOne
- 다대일(N:1) 관계라는 매핑 정보
- 연관관계를 매핑할 때 이렇게 다중성을 나타내는 어노테이션은 필수이다.
속성 | 기능 | 기본값 |
optional | false로 설정하면 연관된 연관된 엔티티가 항상 있어야 한다. | true |
fetch | 글로벌 페치 전략을 설정한다. | @ManyToOne=FetchType.EAGER @OneToMany=FetchType.LAZY |
cascade | 영속성 전이 기능을 사용한다. | |
targetEntity | 연관된 엔티티의 타입 정보를 설정한다. 이 기능은 거의 사용하지 않는다. 컬렉션을 사용해도 제네릭으로 타입 정보를 알 수 있다. ex) @OneToMany private List<Member> members; @OneToMany(targetEntity=Member.class) private List members; |
- @JoinColumn
- 외래 키를 매핑할 때 사용한다.
- name 속성에는 매핑할 외래 키 이름을 지정한다.
속성 | 기능 | 기본값 |
name | 매핑할 외래 키 이름 | 필드명 +_+ 참조하는 테이블의 기본 키 컬럼명 ex) 필드명(team), 참조하는 데이블의 컬럼명(TEAM_ID) => team_TEAM_ID |
referencedColumnName | 외래 키가 참조하는 대상 테이블의 컬럼명 | 참조하는 테이블의 기본키 컬럼명 |
foriegnKey(DDL) | 외래 키 제약조건을 직접 지정할 수 있다. 이 속성은 테이블을 생성할 때만 사용한다. |
|
unique nullable insertable updateable columnDefinition table |
@Column의 속성과 같다. |
(2) 연관관계 등록, 조회
//단방향 연관관계
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("member1");
member.setTeam(team);
em.persist(member);
em.flush(); //insert 쿼리가 나간다.
em.clear();
//영속성 context를 초기화 해주었기 때문에 뒤에 나오는 em.find() 메소드를 통해 select 쿼리가 나간다.
Member findMember = em.find(Member.class, member.getId());
Team findTeam = findMember.getTeam(); //객체 그래프 탐색
System.out.println(findTeam.getName());
2. 양방향 연관관계
- 객체 연관관계
- 팀에서 회원은 일대다 관계이다.
- 일대다 관계는 여러 건과 연관관계를 맺을 수 있으므로 컬렉션을 사용해야 한다.
- List 컬렉션 추가
- 데이블 관계
- 데이터베이스 테이블은 외래 키 하나의 양방향으로 조회할 수 있다.
- 데이터베이스에 추가할 내용은 전혀 없다.
(1) 객체 관계 매핑
@Entity
@Getter @Setter
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "team") //Member class의 team 변수와 연관관계 매핑되어있다.
private List<Member> members = new ArrayList<>();
}
- Member Entity는 변경한 부분이 없다.
- Team Entity에는 List<Member> members를 추가
- @OneToMany 매핑 정보 사용
- mappedBy 속성은 양방향 매핑일 때 사용하는데 반대쪽 매핑의 필드 이름을 값으로 주면 된다.
(2) 연관관계 등록, 조회
//양방향 연관관계
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("member1");
member.setTeam(team);
em.persist(member);
em.flush();
em.clear();
Member findMember = em.find(Member.class, member.getId());
List<Member> members = findMember.getTeam().getMembers(); //객체 그래프 탐색
for (Member m : members) {
System.out.println(m.getName());
}
- 회원 컬렉션으로 객체 그래프 탐색을 사용할 수 있다.
3. 연관관계 주인
(1) 연관관계 주인의 필요성
- 단순히 @OneToMany만 있으면 되지 mappedBy는 왜 필요할까?
- 객체 연관관계 : 2개
- 회원 -> 팀 연관관계 1개 (단방향)
- 팀 -> 회원 연관관계 1개 (단방향)
- 테이블 연관관계 = 1개
- 회원 <-> 팀의 연관관계 1개
- 엔티티를 양방향 연관관계로 설정하면 객체의 참조는 둘인데 외래 키는 하나다. 둘 사이에 차이가 발생한다.
- 두 객체 연관관계 중 하나를 정해서 테이블의 외래 키를 관리해야 하는데 이것을 연관관계의 주인이라 한다.
(2) 연관관계 주인
- 두 연관관계 중 하나를 연관관계의 주인으로 정해야 한다.
- 연관관계의 주인만이 데이터베이스 연관관계와 매핑되고 외래 키를 관리(등록, 수정, 삭제)할 수 있다.
- 주인이 아니면 mappedBy 속성을 사용해서 속성의 값으로 연관관계의 주인을 지정해야 한다.
(3) 연관관계 주인 정하기
- 연관관계의 주인을 정한다는 것은 사실 외래 키 관리자를 선택하는 것이다.
- 만약 회원 엔티티에 있는 Member.team을 주인으로 선택하면 자기 테이블에 있는 외래 키를 관리한다.
- 하지만 팀 엔티티에 있는 Team.members를 주인으로 선택하면 물리적으로 전혀 다른 테이블의 외래 키를 관리해야 한다.
연관관계의 주인은 외래 키가 있는 곳으로 정하자!
- 연관관계의 주인만 데이터베이스 연관관계와 매핑되고 외래 키를 관리할 수 있다.
- 주인이 아닌 반대편은 읽기만 가능하고 외래 키를 변경하지 못한다.
4. 양방향 연관관계의 주의점
(1) 역방향(주인이 아닌 방향)만 값을 입력
// 양방향 연관관계와 연관관계 주인 - 주의
Member member = new Member();
member.setName("member1");
em.persist(member);
Team team = new Team();
team.setName("TeamA");
team.getMembers().add(member); //읽기 전용이다. 값을 넣어줘도 DB에 반영되지 않는다.
em.persist(team);
- 데이버베이스에서 MEMBER 테이블을 조회하게 되면 TEAM_ID가 null 값이 입력되어 있다.
- 연관 관계의 주인이 아닌 쪽에서 값을 수정해도 DB에 반영되지 않는다.
- 연관관계의 주인만이 외래 키의 값을 변경할 수 있다!
(2) 순수한 객체까지 고려한 양방향 연관관계
- 연관관계의 주인에만 값을 저장하고 주인이 아닌 곳에는 값을 저장하지 않아도 될까?
- 객체 관점에서 양쪽 방향에 모두 값을 입력해주는 것이 가장 안전한다.
// 양방향 연관관계와 연관관계 주인
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("member1");
member.setTeam(team); //연관관계의 주인
team.getMembers().add(member); //주인이 아니다. 저장 시 사용되지 않는다.
em.persist(member);
- Test 케이스 작성 시에 JPA를 이용하지 않고 Test를 하는 경우 필요
- 객체까지 고려해서 아닌 곳에도 값을 입력하는 것이 좋다.
- 객체의 양방향 연관관계는 양쪽 모두 관계를 맺어주자.
(3) 연관관계 편의 메소드
- 양방향 연관관계는 양쪽 다 신경 써야 한다.
- 호출하다 보면 실수로 둘 중 하나만 호출해서 양방향이 깨질 수 있다.
- 양방향 관계에서 두 코드는 하나인 것처럼 사용하는 것이 안전하다.
@Entity
@Getter @Setter
public class Member {
...
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
public void changeTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
}
- changeTeam 메소드 하나로 양방향 관계를 모두 설정하도록 변경하였다.
- 이렇게 한 번에 양방향 관계를 설정하는 메소드를 연관관계 편의 메소드라 한다.
5. 정리
- 단방향 매핑만으로 테이블과 객체의 연관관계 매핑은 이미 완료되었다.
- 우선 단방향 매핑만 하고 양방향은 필요할 때 추가해도 된다. (테이블에 영향을 주지 않는다.)
- 객체 그래프 탐색 기능(JPQL 쿼리 탐색)이 필요할 때 양방향 기능을 추가하자.
- 연관관계의 주인은 항상 외래 키가 있는 곳으로 정하자!
- 양방향 연관관계를 매핑하려면 객체에서 양쪽 방향을 모두 관리해야 한다.
반응형
'JPA > JPA' 카테고리의 다른 글
[JPA] 상속 관계 매핑 (0) | 2022.01.03 |
---|---|
[JPA] 연관관계 매핑 - 2 (0) | 2022.01.02 |
[JPA] 엔티티 매핑 (Entity Mapping) (0) | 2021.12.31 |
[JPA] 영속성 컨텍스트 (Persistence context) (0) | 2021.12.29 |
JPA 사용하기 (feat. Maven) (0) | 2021.12.28 |
댓글