해당 글은 김영한 님의 자바 ORM 표준 JPA 프로그래밍을 참고하여 작성한 글입니다.
✅ 단방향 연관관계
▶ 객체 연관관계 VS 테이블 연관관계
- 객체 연관관계
- 회원 객체(Member)는 Member.team 필드(멤버 변수)로 팀 객체와 연관관계를 맺는다.
- 회원 객체와 팀 객체는 단방향 관계이다. 회원은 Member.team 필드를 통해서 팀을 알 수 있지만 반대로 팀은 회원을 알 수 없다.
- 테이블 연관관계
- 회원 테이블은 TEAM_ID 외래 키로 팀 테이블과 연관관계를 맺는다.
- 회원 테이블과 팀 테이블은 양방향 관계이다. 회원 테이블의 TEAM_ID 외래 키를 통해서 회원과 팀을 조인할 수 있고, 반대로 팀과 회원도 조인할 수 있다.
- 객체 연관관계와 테이블 연관관계의 가장 큰 차이
- 참조를 통한 연관관계는 항상 단방향이다. 따라서 객체 간에 연관관계를 양방향으로 만들고 싶으면 반대쪽에도 필드를 추가해서 참조를 보관해야 한다. 이렇게 양쪽에서 서로 참조하는 것을 양방향 관계라고 한다. 하지만 정확히 이야기하면 이것은 양방향 관계가 아니라 서로 다른 단방향 관계 2개이다.
- 반면에 테이블은 외래 키 하나로 양방향으로 조인할 수 있다.
- 객체 연관관계 VS 테이블 연관관계 정리
- 객체는 참조(주소)로 단방향 연관관계를 맺는다.
- 테이블은 외래 키로 양방향 연관관계를 맺는다.
▶ 연관관계 사용
🔽 Member, Team class
@Entity
@Getter @Setter
public class Member {
@Id @GeneratedValue
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
private Long id;
private String name;
}
🔽 저장
//팀 저장
Team team = new Team();
team.setName("팀1");
em.persist(team);
//회원 저장
Member member = new Member();
member.setName("회원1");
member.setTeam(team); //단방향 연관관계 설정, 참조 저장
em.persist(member);
🔽 조회 - 객체 그래프 탐색
Member findMember = em.find(Member.class, member.getId());
Team findTeam = findMember.getTeam(); //객체 그래프 탐색을 통한 연관관계 조회
🔽 수정
// 새로운 팀2
Team team2 = new Team();
team2.setName("팀2");
em.persist(team2);
// 회원1에 새로운 팀2 설정
member.setTeam(team2);
✅ 양방향 연관관계
▶ 객체 연관관계 VS 테이블 연관관계
- 객체 연관관계
- 회원에서 팀은 다대일 관계이고, 팀에서 회원은 일대다 관계이다.
- 회원 ➡️ 팀 (Member.team)
- 팀 ➡️ 회원 (Team.members)
- 회원에서 팀은 다대일 관계이고, 팀에서 회원은 일대다 관계이다.
- 테이블 연관관계
- 회원 테이블은 TEAM_ID 외래 키로 팀 테이블과 연관관계를 맺는다.
- 회원 테이블과 팀 테이블은 양방향 관계이다. 회원 테이블의 TEAM_ID 외래 키를 통해서 회원과 팀을 조인할 수 있고, 반대로 팀과 회원도 조인할 수 있다.
▶ 연관관계 사용
🔽 Member, Team class
@Entity
@Getter @Setter
public class Member {
@Id @GeneratedValue
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
private Long id;
private String name;
//추가
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<Member>();
}
🔽 조회
Team findTeam = em.find(Team.class, team.getId());
List<Member> findMembers = findTeam.getMembers(); // (팀 -> 회원) 객체 그래프 탐색
for (Member member : findMembers) {
System.out.println("member.username = " + member.getUsername());
}
▶ 연관관계의 주인
- 양방향 연관관계 매핑의 규칙
- 양방향 연관관계 매핑 시 두 연관관계 중 하나를 연관관계 주인으로 정해야 한다. 엔티티를 양방향 연관관계로 설정하면 객체의 참조는 둘인데 외래 키가 하나가 되기 때문에 둘 사이에 차이가 발생하게 되기 때문이다.
- 연관관계 주인만이 데이터베이스 연관관계와 매핑되고 외래 키를 관리(등록, 수정, 삭제)할 수 있다. 반면에 주인이 아닌 쪽은 읽기만 할 수 있다.
- 연관관계 주인 지정 방법
- 연관관계 주인은 mappedBy 속성을 사용하지 않는다.
- 주인이 아니면 mappedBy 속성을 사용해서 속성의 값으로 연관관계 주인을 지정해야 한다.
- 연관관계의 주인은 외래 키가 있는 곳으로 정해야 한다.
데이터베이스 테이블의 다대일, 일대다 관계에서는 항상 다 쪽이 외래 키를 가진다. 다 쪽인 @ManyToOne은 항상 연관관계의 주인이 되므로 mappedBy 속성이 없다.
연관관계의 주인을 정한다는 것은 외래 키 관리자를 선택하는 것이다. 만약 Member.team을 주인으로 선택하면 자신의 테이블에 있는 외래 키를 관리하면 된다. 하지만 Team.members를 주인으로 선택하면 물리적으로 전혀 다른 테이블의 외래 키를 관리해야 한다. 이 경우 Team.members가 있는 Team 엔티티는 TEAM 테이블에 매핑되어 있는데 관리해야 할 외래 키는 MEMBER에 있기 때문이다.
▶ 양방향 연관관계 저장 - 주인이 아닌 곳은 외래 키에 영향을 주지 않는다.
🔽 팀1, 회원1 저장
// 팀1 저장
Team team1 = new Team();
team1.setName("팀1");
em.persist(team1);
// 회원1 저장
Member member1 = new Member();
member1.setName("회원1");
member.setTeam(team1); // 연관관계 설정 member1 -> team1
em.persist(member1);
🔽 데이터베이스 조회
MEMBER_ID | USERNAME | TEAM_ID |
member1 | 회원1 | team1 |
- 주인이 아닌 방향은 값을 설정하지 않아도 데이터베이스에 외래 키 값이 정상 입력된다.
- 주인이 아닌 곳에 입력된 값은 외래 키에 영향을 주지 않는다.
- team1.getMembers().add(member1); // 무시(연관관계의 주인이 아님)
➡️ Team.members는 연관관계 주인이 아니기 때문에 이러한 코드는 무시된다.
- team1.getMembers().add(member1); // 무시(연관관계의 주인이 아님)
▶ 양방향 연관관계의 주의 사항
- 연관관계 주인만이 외래 키의 값을 변경할 수 있기 때문에, 연관관계의 주인이 아닌 곳에만 값을 저장하면 정상적으로 저장되지 않는다.
- 객체 관점에서 주인과 주인이 아닌 곳 모두 값을 입력해 주는 것이 안전하다.
Team team1 = new Team("team1", "팀1");
Member member1 = new Member("member1", "회원1");
member1.setTeam(team1); //연관관계 설정 member1 -> team1
team1.getMembers().add(member1); //연관관계 설정 team1 -> member1
▶ 연관관계 편의 메서드
양방향 연관관계는 결국 양쪽 다 신경 써야 한다. member.setTeam(team)과 team.getMembers().add(member)를 각각 호출하다 보면 실수로 둘 중 하나만 호출해서 양방향이 깨질 수 있다. 이럴 때 한 번에 양방향 관계를 설정하는 메서드를 사용하면 되는데, 이것을 연관관계 편의 메서드라고 한다.
🔽 연관관계 편의 메서드 추가 코드
public class Member{
private Team team;
//연관관계 편의 메서드
public void addTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
// ...
}
🔽 연관관계 편의 메서드 사용 코드
Team team1 = new Team();
team1.setName("팀1");
em.persist(team1);
Member member1 = new Member();
member1.setName("회원1");
member1.addTeam(team1); //연관관계 편의 메서드 사용
em.persist(member1);
▶ 연관관계 편의 메서드 작성 시 주의 사항
- team2로 변경할 때 team1 ➡️ member1 관계를 제거하지 않았기 때문에 team1.getMember() 메서드를 실행했을 때 member1이 남아있다. 따라서 연관관계를 변경할 때 기존 팀이 있으면 기존 팀과 회원의 연관관계를 삭제하는 코드를 추가해야 한다.
member1.addTeam(team1);
member1.addTeam(team2);
Member findMember = team1.getMember(); //member1이 여전히 조회된다.
public void addTeam(Team team){
if(this.team != null) { // this.team != null이면 이 member객체는 team이 있음을 의미
this.team.getMembers().remove(this); // 해당 팀의 멤버에서 삭제
}
this.team = team;
team.getMembers().add(this);
}
📍 참고
'Programming > JPA' 카테고리의 다른 글
[JPA] 고급 매핑(상속관계 매핑) - 엔티티(Entity) 매핑 (7) (0) | 2024.07.25 |
---|---|
[JPA] 다양한 연관관계 매핑 - 엔티티(Entity) 매핑 (6) (1) | 2024.07.25 |
[JPA] 연관관계 매핑 - 엔티티(Entity) 매핑 (4) (1) | 2024.07.25 |
[JPA] 데이터베이스 스키마 자동 생성 (0) | 2024.07.25 |
[JPA] 필드와 컬럼 매핑 - 엔티티(Entity) 매핑 (3) (0) | 2024.07.25 |