이 글에서는 JPA 연관관계 매핑(Relationship Mapping)에 대해 설명한다.
@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 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의 속성과 같다. |
//단방향 연관관계
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.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<>();
}
2.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. 연관관계 주인의 필요성
회원 -> 팀 연관관계 (단방향)
팀 -> 회원 연관관계 (단방향)
회원 <-> 팀
의 연관관계로 표현된다. 엔티티를 양방향 연관관계로 설정하면 객체의 참조는 두 개지만, 외래 키는 하나만 존재하여 차이가 발생한다. 따라서 두 객체 연관관계 중 하나를 정해서 테이블의 외래 키를 관리해야 하는데, 이를 연관관계의 주인이라고 한다.3.2. 연관관계 주인
mappedBy
속성을 사용하여 연관관계의 주인을 지정해야 한다.3.3. 연관관계 주인 정하기
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);
4.2. 순수한 객체까지 고려한 양방향 연관관계
연관관계의 주인에만 값을 저장하고 주인이 아닌 곳에는 값을 저장하지 않아도 될까? 객체 관점에서는 양쪽 방향에 모두 값을 입력해주는 것이 가장 안전하다. 이는 JPA를 이용하지 않고 Test 케이스를 작성하는 경우에도 필요하다. 객체까지 고려하여 주인이 아닌 곳에도 값을 입력하는 것이 좋으며, 객체의 양방향 연관관계는 양쪽 모두 관계를 맺어주는 것이 바람직하다.
// 양방향 연관관계와 연관관계 주인
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);
(3) 연관관계 편의 메소드
양방향 연관관계는 양쪽 모두 신경 써야 한다. 호출하다 보면 실수로 둘 중 하나만 호출하여 양방향 관계가 깨질 수 있다. 양방향 관계에서 두 코드를 하나인 것처럼 사용하는 것이 안전하다. 예를 들어, changeTeam 메소드 하나로 양방향 관계를 모두 설정하도록 변경할 수 있다. 이렇게 한 번에 양방향 관계를 설정하는 메소드를 연관관계 편의 메소드라고 한다.
@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);
}
}