자바 ORM 표준 JPA 프로그래밍 학습 내용 정리한 포스팅 입니다.
양방향 연관관계
일대다 관계에서는 여러 건과 연관관계를 맺을 수 있으므로 컬렉션(Collection
, Set
, Map
, List
..) 을 사용한다.
양방향 연관관계 매핑
회원 엔티티 (Member.java)
회원 엔티티에는 변경할 사항이 없다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@Entity
public class Member {
@Id
@Column(name = "MEMBER_ID")
private String id;
private String username;
// 연관관계 매핑
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
// 연관관계 설정
public void setTeam(Team team) {
this.team = team;
}
// Getter, Setter...
}
|
매핑한 팀 엔티티 (Team.java)
팀과 회원은 1:N 이며 해당 다중성을 매핑하기 위하여 @OneToMany
매핑 정보를 사용한다. 또한 mappedBy
속성은 양방향 매핑일 때 사용하며, 반대쪽 매핑되는 필드의 이름을 값으로 설정하면 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@Entity
public class Team {
@Id
@Column(name = "TEAM_ID")
private String id;
private String name;
@OneToMany (mappedBy = "team")
private List<Member> members = new ArrayList<Member>();
// Getter, Setter ...
}
|
일대다 컬렉션 조회
1
2
3
4
5
6
7
8
9
|
public static void biDirection(EntityManager em) {
Team team = em.find(Team.class, "team1");
List<Member> members = team.getMembers(); // 팀 -> 회원 방향으로 객체 그래프를 탐색한다.
for (Member member : members) {
System.out.println("member.username : " + member.getUsername());
}
}
|
연관관계 주인
- 테이블은 외래 키 하나로 두 테이블의 연관관계를 관리하지만, 객체의 경우 엔티티를 양방향으로 매핑하기 위해서는 객체의 참조를 통해서 서로 참조해야만 한다. (ex 회원->팀, 팀->회원)
- 엔티티를 양방향 연관관계로 설정하면 객체 참조는 둘인데 외래키는 하나이므로 둘 사이에 차이가 발생
- 두 객체 연관관계 중 하나를 정해서 테이블의 외래키를 관리해야 하는데 이것을 연관관계의 주인 이라고 한다.
양방향 매핑의 규칙 : 연관관계의 주인
- 연관관계의 주인만이 데이터베이스 연관관계와 매핑되고 외래 키를 관리(등록, 수정, 삭제) 할 수 있다. 주인이 아닌 쪽은 읽기만 가능하다.
- 주인은
mappedBy
속성을 사용하지 않는다.
- 연관관계의 주인을 정한다는 것 -> 외래키 관리자를 선택하는 것
연관관계의 주인은 외래 키가 있는 곳
- 연관관계의 주인만 데이터베이스 연관관계와 매핑되며, 외래키를 관리할 수 있다.
- 주인이 아닌 반대편 (
inverse
, non-owning side
) 은 읽기만 가능 외래키를 변경하지는 못한다.
N:1, 1:N 에서는 항상 N 쪽이 외래 키를 갖는다. @ManyToOne
은 항상 연관관계의 주인이 되므로, mappedBy 속성이 존재하지 않는다.
양방향 연관관계의 주의점
- 연관관계의 주인에는 값을 입력하지 않고, 주인이 아닌곳에만 입력하는 실수를 유의해야 한다.
- 예제코드에서 Member 가 연관관계의 주인인데 Member.team 의 연관관계에 대해서 설정하지 않았다. 따라서 TEAM_ID 외래키의 값도 null 이 저장된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public static void testSave(EntityManager em) {
// 회원1 저장
Member member1 = new Member("member1", "회원1");
em.persist(member1);
// 회원2 저장
Member member2 = new Member("member2", "회원2");
em.persist(member2);
// 팀1 저장
Team team1 = new Team("team1", "팀1");
// 주인이 아닌곳에 연관관계 설정
team1.getMembers().add(member1);
team1.getMembers().add(member2);
em.persist(team1);
}
|
순수한 객체까지 고려한 양방향 연관관계
- 객체 관점에서 양쪽 방향에 참조 값을 입력해 주는 것이 가장 안전하다.
Member.team
: 연관관계의 주인으로 값으로 외래키를 관리
Team.members
: 연관관계 주인 아님. 따라서 저장시에는 사용되지 않음.
- 결론 : 객체의 양방향 연관관계는 양쪽 모두 관계를 맺어주자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// 양방향 연관관계 사용하여 저장
public static void testORM_양방향(EntityManager em) {
Team team2 = new Team("team2", "팀2");
em.persist(team2);
Member member3 = new Member("member3", "회원3");
member3.setTeam(team2);
team2.getMembers().add(member3); // 연관관계 설정 member1 -> team2
em.persist(member3); // 연관관계 설정 team2 -> member1
Member member4 = new Member("member4", "회원4");
member4.setTeam(team2); // 연관관계 설정 member4 -> team2
team2.getMembers().add(member4); // 연관관계 설정 team2 -> member4
em.persist(member4);
}
|
연관관계 편의 메소드
- 양방향 연관관계는 양쪽 다 신경 써야 한다. 그러나 각각 호출하다보면 실수로 둘 중 하나만 호출해서 양방향이 깨질 수 있다.
setTeam()
메소드 하나로 양방향 모두를 설정하도록 변경한다.
- 한 번에 양항뱡 관계를 설정하는 메소드를 연관관계 편의 메소드라고 한다.
1
2
3
4
5
|
// Member.java 의 setTema() 메소드 수정
public void setTeamNew(Team team) {
this.team = team;
team.getMembers().add(this);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public static void testORM_양방향_리펙토링(EntityManager em) {
Team team3 = new Team("team3", "팀3");
em.persist(team3);
Member member5 = new Member("member5", "회원5");
member5.setTeam(team3);
em.persist(member5);
Member member6 = new Member("member6", "회원6");
member6.setTeam(team3);
em.persist(member6);
}
|
연관관계 편의 메소드 작성 시 주의 사항
- 연관관계를 변경할 경우 기존 연관관계를 삭제하는 코드를 추가해야 한다.
- 객체에서 양방향 연관관계를 사용하려면 로직을 견고하게 작성해야 한다.
1
2
3
4
5
6
7
8
9
10
11
|
// 연관관계 편의 메소드
public void setTeam(Team team) {
// 기존 팀과 관계를 제거
if(this.team != null) {
this.team.getMembers().remove(this);
}
this.team = team;
team.getMembers().add(this);
}
|
정리
- 단방향 매핑과 비교했을때 양방향 매핑은 복잡하고, 연관관계의 주인도 정해야하고 단방향 연관관계를 양방향으로 만들기 위해 로직도 잘 관리해야한다.
- 양방향의 장점은 반대방향으로 객체 그래프 탐색 기능이 추가 된것 뿐이다.
- 양방향 연관관계를 매핑하려면 객체에서 양쪽 방향을 모두 관리해야한다.
연관관계의 주인을 정하는 기준
- 비즈니스 로직의 중요도가 크다고 해서 무조건 연관관계의 주인이 되는 것은 아니다.
- 비즈니스 중요도를 배제하고 단순히 외래 키 관리자 정도의 의미만 부여해야 한다.
- 연관관계의 주인은 외래키의 위치와 관련해서 정해야 한다.