본 글은 김영한님의 <자바 ORM 표준 JPA 프로그래밍>을 읽고 공부한 내용을 정리한 글입니다.
- 단방향 연관관계
- 연관관계 사용
- 양방향 연관관계
- 연관관계의 주인
- 양방향 연관관계 저장
- 양방향 연관관계의 주의점
객체는 참조(주소)로 연관관계를 맺고, 테이블은 외래 키로 연관관계를 맺습니다.
테이블은 외래 키 하나로 양방향 조인이 가능하지만, 참조를 통한 연관관계는 언제나 단방향입니다.
따라서 양방향 관계를 만들고 싶다면 서로 다른 단방향 관계 2개를 만들어야합니다.
객체의 Member.team과 테이블의 MEMBER.TEAM_ID를 매핑하는 것이 연관관계 매핑입니다.
@Entity
public class Member {
...
@ManyToOne
@JoinColumn(name="TEAM_ID)
private Team team;
...
}
@Entity
public class Team {
@Id
@Column(name="TEAM_ID")
private String id;
}
@ManyToOne
: 다대일 연관관계를 나타냅니다. 생략 불가!
@JoinColumn(name="TEAM_ID)
: 외래 키를 매핑합니다. 생략시 {필드명 + _ + 참조 테이블의 컬럼명} 외래 키를 사용합니다.
Member member1 = new Member("member1", "회원1");
member1.setTeam(team1); // 연관관계 설정 member1 -> team1
em.persist(member1);
JPA는 참조한 팀의 식별자(Id)를 외래 키로 사용해서 적절한 등록 쿼리를 생성합니다.
❗️JPA에서 엔티티를 저장할 때 연관된 모든 엔티티는 영속 상태여야 합니다.
Member member = em.find(Member.class, "member1");
Team team = member.getTeam(); // 객체 그래프 탐색
이처럼 객체를 통해 연관된 엔티티를 조회하는 것이 객체 그래프 탐색이다.
select m from Member m join m.team t
where t.name=:teamName
조인 JPQL은 위와 같습니다. :teamName과 같이 :로 시작하는 것은 파라미터를 바인딩받는 문법입니다.
Member member = em.find(Member.class, "member1");
member.setTeam(team2); // 회원1의 팀 수정(team1 -> team2)
이렇게 엔티티의 값만 변경해두면 변경 감지 기능으로 인해 자동으로 데이터 베이스에 반영됩니다.
Member member = em.find(Member.class, "member1");
member.setTeam(nul); // 연관관계 제거
연관관계를 null로 설정하면 연관관계가 삭제됩니다.
member.setTeam(null); // 회원 연관관계 제거
em.remove(team); // 팀 삭제
연관된 엔티티를 삭제하려면 기존에 있던 연관관계를 먼저 제거하고 삭제해야 합니다.
이번에는 팀에서도 회원으로 접근할 수 있도록 양방향 연관관계를 매핑해보겠습니다.
팀에서 회원은 일대다 관계이기 때문에 여러 건과 연관관계를 맺을 수 있도록 List 컬렉션을 사용할 것입니다.
데이터베이스 테이블은 외래 키 하나로 양방향으로 조회할 수 있기 때문에 추가할 사항이 없습니다.
@Entity
public class Member {
...
@ManyToOne
@JoinColumn(name="TEAM_ID)
private Team team;
...
}
@Entity
public class Team {
@Id
@Column(name="TEAM_ID")
private String id;
@OneToMany(mappedBy="team") // 일대다 연관관계
private List<Member> members = new ArrayList<Member>();
}
@OneToMany(mappedBy="team")
: 일대다 관계를 매핑합니다. mappedBy 속성에는 반대쪽 매핑의 필드 이름 값을 줍니다. 이 경우에는 Member.team이므로 team을 값으로 주었습니다.
테이블은 외래 키 하나로 두 테이블의 연관관계를 관리합니다.
엔티티를 양방향으로 매핑하면 두 곳에서 서로를 참조하기 때문에 객체의 참조는 둘인데 외래 키는 하나인 상황이 발생합니다.
이 때 두 객체 연관관계 중 하나를 정해서 테이블의 외래 키를 관리하는 것을 연관관계의 주인이라고 합니다.
연관관계의 주인만이 데이터베이스 연관관계와 매핑되고 외래 키를 관리할 수 있습니다.
반면에 주인이 아닌 쪽은 읽기만 할 수 있습니다.
연관관계의 주인을 정한다는 것은 외래 키 관리자는 선택하는 것이기 때문에 외래 키를 가진 Member.team을 주인으로 정해야 한다.
Member member1 = new Member("member1", "회원1");
member1.setTeam(team1); // 연관관계 설정 member1 -> team1
em.persist(member1);
team1.getMembers().add(member1);
과 같은 코드가 있어야할 것 같지만 Team.members는 연관관계의 주인이 아니기 때문에 무시됩니다.
객체 관점에서 양쪽 방향에 모두 값을 입력해주는 것이 가장 안전합니다.
Member member1 = new Member("member1", "회원1");
// 양방향 연관관계 설정
member1.setTeam(team1); // 연관관계의 설정 member1 -> team1
team1.getMembers().add(member1); // 연관관계의 설정 team1 -> member1
em.persist(member1);
Member.team
: 연관관계의 주인이며 이 값으로 외래 키를 관리합니다.
Team.members
: 주인이 아니므로 저장 시에 사용되지 않습니다.
객체의 양방향 연관관계는 양쪽 모두 관계를 맺어줍시다!
양방향을 연결하는 코드를 리팩토링해보겠습니다.
@Entity
public class Member {
...
public Team;
public void setTeam(Team team) {
if (this.team != null) {
this.team.getMembers().remove(this);
}
this.team = team;
team.getMembers().add(this);
}
...
}