•
객체의 참조와 테이블의 외래 키를 매핑하는 것이 이 장의 목표다.
•
다음은 연관 관계 매핑을 이해하기 위한 핵심 키워드다.
◦
방향: 단방향, 양방향
▪
한 쪽만 참조하는 것을 단방향 관계, 양쪽 모두 서로 참조하는 것을 양방향 관계라 한다.
▪
방향은 객체 관계에만 존재하며 테이블 관계는 항상 양방향이다.
◦
다중성: 다대일, 일대다, 일대일, 다대다
◦
연관관계의 주인: 객체를 양방향 연관관계로 만드는 경우, 연관관계의 주인을 정해야 한다.
1. 단방향 연관관계
•
연관관계 중에선 다대일 단방향 관계를 가장 먼저 이해해야 한다. 다음은 예시다.
◦
회원과 팀이 있다.
◦
회원읜 하나의 팀에만 소속될 수 있다.
◦
회원과 팀은 다대일 관계다.
•
객체 연관관계
◦
회원 객체는 team 속성으로 팀 객체와 연관관계를 맺는다.
◦
회원 객체와 팀 객체는 단방향 관계다. 회원 객체는 내부의 team 속성으로 팀 객체에 접근할 수 있지만 반대는 불가능하다.
•
테이블 연관관계
◦
회원 테이블은 TEAM_ID 외래 키로 팀 테이블과 연관관계를 맺는다.
◦
회원 테이블과 팀 테이블은 양방향 관계다. 회원 테이블의 TEAM_ID 외래 키를 통해서 회원과 팀을 조인할 수 있고 반대로 팀과 회원도 조인할 수 있다.
•
객체 연관관계와 테이블 연관관계의 가장 큰 차이
◦
참조를 통한 연관관계는 언제나 단방향으로 객체간에 연관관계를 양방향으로 만드려면 피참조 객체에도 속성을 추가해서 참조를 보관해야 한다.
▪
결국 연관관계를 하나 더 만들어야 한다. 이는 양방향이 아닌 서로 다른 단방향 관계가 2개인 것이다.
•
객체 연관관계와 테이블 연관관계 정리
◦
객체는 참조로 연관관계를 맺는다.
◦
테이블은 외래 키로 연관관계를 맺는다.
1. 순수한 객체 연관관계
•
순수한 객체는 참조를 사용하여 연관관계를 탐색할 수 있는데 이것을 객체 그래프 탐색이라 한다.
2. 테이블 연관관계
•
데이터베이스는 외래 키를 사용해서 연관관계를 탐색할 수 있는데 이를 조인이라 한다.
3. 객체 관계 매핑
•
연관관계 매핑을 위한 어노테이션들을 이용해 JPA에서 두 객체를 매핑할 수 있다.
◦
@ManyToOne: 이름 그대로 다대일 관계라는 매핑 정보다. 회원과 팀은 다대일 관계로 연관관계 매핑 시 다중성을 나타내는 어노테이션을 필수로 사용해야 한다.
◦
@JoinColumn(name=”TEAM_ID”): 조인 컬럼은 외래 키를 매핑할 때 사용한다. name 속성 값으로 매핑할 외래 키 이름을 제공하며 이 어노테이션은 생략 가능하다.
4. @JoinColumn
•
외래 키를 매핑할 때 사용한다.
•
속성은 다음과 같다.
◦
name: 매핑할 외래 키 이름
◦
referencedColumnName: 외래 키가 참조하는 대상 테이블의 컬럼명
◦
foreginKey(DDL): 외래 키 제약 조건을 지정할 수 있으며 테이블을 생성할 때만 사용한다.
◦
unique, nullabe, insertable, updatable, columnDefinition, table: @Column의 속성과 같다.
•
생략하는 경우, 외래 키를 찾을 때 기본 전략을 사용한다.
◦
속성명 + _ + 참조하는 테이블의 컬럼명
5. @ManyToOne
•
다대일 관계에서 사용한다.
•
속성은 다음과 같다.
◦
optional: false인 경우 연관된 엔티티가 항상 존재해야 한다.
◦
fetch: 글로벌 페치 전략을 설정한다.
▪
기본값은 FetchType.EAGER다.
◦
cascade: 영속성 전이 기능을 사용한다.
◦
targetEntity: 연관된 엔티티의 타입 정보를 설정한다. 이 기능은 거의 사용하지 않는다.
2. 연관관계 사용
•
연관관계를 등록, 수정, 삭제, 조회하는 예제를 통해 연관관계를 어떻게 사용하는지 알아보자.
1. 저장
public void testSave() {
Team team1 = new Team("team1", "팀1");
em.persist(team1);
Member member1 = new Member("member1", "회원1");
member1.setTeam(team1);
em.persist(member1);
}
Java
복사
•
회원 객체는 팀 객체를 참조하고 저장했다. JPA는 참조한 팀의 식별자를 외래 키로 이용해 적절한 등록 쿼리를 생성한다.
2. 조회
•
연관관계가 있는 엔티티를 조회하는 방법은 크게 2가지다.
◦
객체 그래프 탐색
▪
member.getTeam()을 사용해서 member와 관련된 team 엔티티를 조회할 수 있다.
◦
객체지향 쿼리(JPQL) 사용
3. 수정
private static void updateReleation(EntityManager em) {
Team team2 = new Team("team2", "팀2");
em.persist(team2);
Member member = em.find(Member.class, "member1");
member.setTeam(team2);
}
Java
복사
•
단순히 불러온 엔티티 값만 변경해두면 트랜잭션을 커밋할 때 플러시가 발생하면서 변경 감지 기능이 작동한다. 그리고 변경 사항을 데이터베이스에 자동으로 반영한다.
4. 연관관계 제거
private static void updateReleation(EntityManager em) {
Member member = em.find(Member.class, "member1");
member.setTeam(null);
}
Java
복사
•
위와 같이 작성하면 연관관계가 제거된다.
5. 연관된 엔티티 삭제
•
연관된 엔티티를 삭제하려면 기존에 있던 연관관계를 먼저 제거한 후, 삭제해야 한다. 그렇지 않으면 외래 키 제약조건으로 인해 데이터베이스에서 오류가 발생한다.
member1.setTeam(null);
member2.setTeam(null);
em.remove(team);
Java
복사
3. 양방향 연관관계
1. 양방향 연관관계 매핑
•
회원과 팀은 다대일 관계였으며 팀과 회원은 일대다 관계다. 따라서 팀 엔티티에 List<Member> members 속성이 추가된다.
•
그리고 일대다 관계를 매핑하기 위해 @OneToMany 매핑 정보를 사용한다.
◦
mappedBy 속성은 양방향 매핑일 때 사용하며 반대쪽 매핑의 필드 이름을 값으로 사용한다.
•
이것으로 양방향 매핑을 완료하였고 팀에서 회원 컬렉션으로 객체 그래프를 탐색할 수 있다.
4. 연관관계의 주인
•
mappedBy는 왜 필요한 속성일까?
•
엄밀히 이야기하면 객체에는 양방향 연관관계가 존재하지 않기 때문인데, 서로 다른 단방향 연관관계 2개를 어플리케이션 로직으로 잘 묶어 양방향인 것처럼 보이게 할 뿐이다.
•
반면 데이터베이스 테이블은 앞서 설명했듯 외래 키 하나로 양방향 참조가 가능하다. 외래 키 하나로 두 테이블의 연관관계를 관리하는 것이다.
•
엔티티를 단방향으로 매핑하면 참조를 하나만 사용하므로 이 참조로 외래 키를 관리하면 된다. 그러나 엔티티를 양방향 참조하는 경우, 두 개의 단방향 참조 중 어떤 것을 외래 키로 사용할 지 혼란이 생긴다.
•
이때 두 객체의 연관관계중 하나를 정해서 테이블의 외래 키를 관리하는데, 이것을 연관관계의 주인이라고 한다.
1. 양방향 매핑의 규칙: 연관관계의 주인
•
양방향 연관관계 매핑 시, 두 연관관계 중 하나를 연관관계의 주인으로 정해야 한다.
◦
연관관계의 주인만이 데이터베이스 연관관계와 매핑되고 외래 키를 관리할 수 있다. 반면 주인이 아닌 쪽은 읽기만 할 수 있다.
•
연관관계의 주인을 결정할 때, mappedBy 속성을 사용한다. 속성의 값으로 연관관계의 주인을 지정한다.
2. 연관관계의 주인은 외래 키가 있는 곳
•
연관관계의 주인은 외래 키를 가지고 있는 테이블로 정해야 한다.
6. 양방향 연관관계의 주의점
•
연관관계의 주인에는 값을 입력하지 않고 주인이 아닌 곳에만 값을 입력하는 것이다. 데이터베이스에 외래 키 값이 정상적으로 저장되지 않는다면 가장 먼저 의심해보아야 한다.
public void testSaveNonOwner() {
Member member1 = new Member("member1", "회원1");
em.persist(member1);
Member member2 = new Member("member2", "회원2");
em.persist(member2);
Team team1 = new Team("team1", "팀1");
team1.getMembers().add(member1);
team1.getMembers().add(member2);
em.persits(team1);
}
Java
복사
•
이 경우, Member 테이블의 TEAM_ID가 null로 저장된다. 연관관계의 주인만이 외래 키의 값을 변경할 수 있다.
1. 순수한 객체까지 고려한 양방향 연관관계
•
사실 객체 관점에서 양쪽 방향에 모두 값을 입력해주는 것이 가장 안전하다.
◦
양쪽 방향 모두 값을 입력하지 않으면 JPA를 사용하지 않는 순수한 객체 상태일 때 심각한 문제가 발생할 수 있다.
•
가급적이면 객체의 양방향 연관관계인 경우, 양쪽 모두 관계를 설정해주자.
2. 연관관계 편의 메소드
•
양방향 관계에서 두 단방향 연관관계를 설정하는 코드는 하나인 것처럼 사용하는 것이 안전한다.
public void setTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
...
Java
복사
•
이렇게 한 번에 양방향 관계를 설정하는 메소드를 연관관계 편의 메소드라 한다.
3. 연관관계 편의 메소드 작성 시 주의사항
•
객체에서 양방향 연관관계를 사용하려면 로직을 견고하게 작성해야 한다.
•
만약 기존에 연관관계가 있는 경우, 이를 삭제하고 다시 연결하는 등의 작업 말이다.
7. 정리
•
단방향 매핑과 비교해서 양방향 매핑은 복잡하다.
•
연관관계의 주인도 정해야 하고 두 개의 단방향 연관관계를 양방향으로 만들기 위해 로직도 잘 관리해야 한다.
•
중요한 사실은 연관관계까 하나인 단방향 매핑은 언제나 연관관계의 주인이라는 점이다.
◦
양방향의 장점은 결국 반대방향으로 객체 그래프 탐색 기능이 추가도니 것 뿐이다.