[연관관계 매핑 기초] 양방향 연관관계
객체 연관관계와 테이블 연관관계의 차이
테이블 연관관계는 1개로 방향 개념이 없다.
테이블은 외래키 하나로 두 테이블의 연관관계를 관리한다.
외래키 Member.TEAD_ID로 Member, Team 양쪽을 조인할 수 있다.
객체는 연관관계가 2개이다.
객체를 양방향으로 참조하려면, 단방향 연관관계 2개를 만들어야 한다.
Member와 Team 객체에 각각 참조값을 넣어 양쪽으로 참조를 할 수 있다.
양방향 매핑 규칙
두 개의 참조값 중 하나로 외래키를 관리해야 한다.
Member.team이 바뀌었을 때 TEAM_ID를 바꿔야 할지,
Team.Members가 바뀌었을 때 TEAM_ID를 바꿔야 할지 정해야 한다.
두 객체의 관계 중 하나를 연관관계 주인으로 지정한다.
연관관계 주인만이 외래키를 수정할 수 있다.
외래키가 있는 곳을 연관관계 주인으로 정한다.
외래키는 1:N 중 N에 위치하므로, 여기서는 Member.team이 연관관계 주인이다.
양방향 매핑
Member에서 Team을 참조하기 위해 Member.team을 추가한다.
@JoinColumn을 사용해 테이블의 TEAM_ID와 매핑해 준다.
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
Team에서 Member를 참조하기 위해 Team.members를 추가한다.
연관관계의 주인이 아니기 때문에 mappedBy 옵션으로 주인을 지정한다.
@OneToMany(mappedBy = "team") //Member의 team과 매핑됨
private List<Member> members = new ArrayList<>();
양방향 매핑을 통해 양방향 객체 그래프 탐색이 가능해졌다.
// Team에서 Member 조회
Team findTeam = em.find(Team.class, team.getId());
int memberSize = findTeam.getMembers().size();
// Member에서 Team 조회
Member findMember = em.find(Member.class, member.getId());
List<Member> members = findMember.getTeam().getMembers();
양방향 매핑 주의점
연관관계의 주인이 아닌 쪽에 데이터 삽입
연관관계 주인의 반대편에 데이터를 삽입한 예제이다.
오직 연관관계 주인만이 외래키 값을 변경할 수 있다.
그런데 연관관계 주인에 값을 입력하지 않았으므로 외래키 값이 null이다.
따라서 연관관계 주인에는 반드시 참조값을 대입해야 한다.
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("member1");
//member.setTeam(team);
em.persist(member);
team.getMembers().add(member);
그렇다면 연관관계의 주인에만 값을 저장하고, 주인이 아닌 쪽에는 값을 저장하지 않아도 될까?
사실은 객체 관점에서 양쪽에 모두 값을 입력해 주는 게 안전하다.
그렇지 않으면 JPA를 사용하지 않는 순수한 객체 상태에서 문제가 생길 수 있다.
예를 들어, JPA를 사용하지 않고 엔티티에 대한 테스트 코드를 작성한다고 해보자.
member.getTeam()과 달리 team.getMembers()는 null 값이 나올 수 있다.
public void test(){
Team team1 = new Team("team1", "팀1");
Member memer1 = new Member("member1", "회원1");
Member member2 = new Member("member2", "회원2");
member1.setTeam(team1);
member2.setTeam(team2);
List<Member> members = team1.getMembers();
}
연관관계 편의 메서드를 작성하자
양쪽에 연관관계를 주입하는 메서드를 작성하면 데이터가 누락될 위험이 없어진다.
아래는 Member에 연관관계 편의 메서드를 작성한 예시이다.
public void changeTeam(Team team){
this.team = team;
team.getMembers().add(this);
}
반대로 Team에 Member를 주입하는 연관관계 편의 메서드를 작성할 수도 있다.
두 객체(1, N) 중 어느 쪽에 작성할지는 상황에 따라 결정하면 된다.
public void addMember(Member member){
member.setTeam(this);
members.add(member);
}
양방향 매핑 시에 무한 루프를 조심하자.
양방향 연관관계가 있는 객체 모두에 toString()을 넣으면 무한 루프에 빠진다.
toString 뿐만 아니라 Json 생성 라이브러리를 사용할 때도 무한 루프가 발생한다.
Controller에서 양방향 연관관계를 가진 엔티티를 직접 반환한다면,
값을 Json으로 변경 시 두 엔티티가 묶여서 무한 루프가 일어난다.
따라서 엔티티를 직접 반환하지 말고 DTO로 변환 후 반환하자.
양방향 매핑 정리
단방향 매핑만으로 연관관계 매핑은 완료된 것이다.
양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색) 기능이 추가된 것뿐이다.
객체 입장에서 양방향 매핑은 고려할 사항이 많아진다.
기본적으로 단방향 매핑으로 설계하고, 역방향으로 객체 탐색이 꼭 필요할 때 양방향 매핑을 하자.
양방향 매핑을 추가해도 테이블에는 영향을 주지 않는다.
연관관계 주인은 외래 키의 위치를 기준으로 정하자.
DB에서는 외래키를 관리하는 테이블에서만 연관된 테이블을 수정할 수 있다.
JPA에서는 연관관계 주인인 객체만 연관된 객체를 수정할 수 있다.
따라서 메커니즘을 동일하게 작동시키려면, 외래키가 있는 객체를 연관관계 주인으로 정해야 한다.
외래 키가 위치하지 않은 곳을 연관관계 주인으로 했다면?
예를 들어, Team.members를 연관관계 주인으로 정했다고 가정해보자.
Team.members를 업데이트 했을 때 Member 업데이트 쿼리가 날아간다.
Team을 업데이트했는데, 다른 엔티티 업데이트 쿼리가 날아간 것으로 일관적이지 않다.
Reference
자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의
초급자를 위해 준비한 [웹 개발, 백엔드] 강의입니다. JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자
www.inflearn.com