////
Search
Duplicate
🚉

5장. 연관관계 매핑 기초

객체의 참조와 테이블의 외래 키를 매핑하는 것이 이 장의 목표다.
다음은 연관 관계 매핑을 이해하기 위한 핵심 키워드다.
방향: 단방향, 양방향
한 쪽만 참조하는 것을 단방향 관계, 양쪽 모두 서로 참조하는 것을 양방향 관계라 한다.
방향은 객체 관계에만 존재하며 테이블 관계는 항상 양방향이다.
다중성: 다대일, 일대다, 일대일, 다대다
연관관계의 주인: 객체를 양방향 연관관계로 만드는 경우, 연관관계의 주인을 정해야 한다.

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_IDnull로 저장된다. 연관관계의 주인만이 외래 키의 값을 변경할 수 있다.

1. 순수한 객체까지 고려한 양방향 연관관계

사실 객체 관점에서 양쪽 방향에 모두 값을 입력해주는 것이 가장 안전하다.
양쪽 방향 모두 값을 입력하지 않으면 JPA를 사용하지 않는 순수한 객체 상태일 때 심각한 문제가 발생할 수 있다.
가급적이면 객체의 양방향 연관관계인 경우, 양쪽 모두 관계를 설정해주자.

2. 연관관계 편의 메소드

양방향 관계에서 두 단방향 연관관계를 설정하는 코드는 하나인 것처럼 사용하는 것이 안전한다.
public void setTeam(Team team) { this.team = team; team.getMembers().add(this); } ...
Java
복사
이렇게 한 번에 양방향 관계를 설정하는 메소드를 연관관계 편의 메소드라 한다.

3. 연관관계 편의 메소드 작성 시 주의사항

객체에서 양방향 연관관계를 사용하려면 로직을 견고하게 작성해야 한다.
만약 기존에 연관관계가 있는 경우, 이를 삭제하고 다시 연결하는 등의 작업 말이다.

7. 정리

단방향 매핑과 비교해서 양방향 매핑은 복잡하다.
연관관계의 주인도 정해야 하고 두 개의 단방향 연관관계를 양방향으로 만들기 위해 로직도 잘 관리해야 한다.
중요한 사실은 연관관계까 하나인 단방향 매핑은 언제나 연관관계의 주인이라는 점이다.
양방향의 장점은 결국 반대방향으로 객체 그래프 탐색 기능이 추가도니 것 뿐이다.