※ 인프런 강의 김영한님의 「자바 ORM 표준 JPA 프로그래밍」을 학습한 내용을 정리한 글입니다.
양방향 연관관계와 연관관계의 주인
이전 글에서 회원에서 팀을 조회할 수 있는 다대일 단방향 매핑을 정리하였습니다.
반대로 팀에서 회원에 접근하려면 어떻게 해야 할까요?
이번에는 양방향 연관관계 패밍을 통해 알아보도록 하겠습니다.
✅ 양방향 객체 연관관계에서 Team 객체에 List 형 members가 추가되었다!
회원과 팀은 다대일 관계, 반대로 팀에서 회원은 일대다 관계로 여러 데이터를 한꺼번에 연관관계를 맺으려면 List와 같은 컬렉션을 사용해야 함
❗❓ 테이블 연관관계는 단방향 연관관계일 때와 똑같지 않은가?!
테이블 연관관계는 외래키 하나로 양방향으로 조회 가능
( = DB 테이블은 JOIN으로 모두 조회 가능하기 때문)
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")// 테이블 칼럼명
private String username;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
// getter/setter 생략
}
@Entity
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
// getter/setter 생략
@OneToMany(mappedBy = "team") // team => Member 클래스의 Team 변수명과 동일
private List<Member> members = new ArrayList<Member>();
}
Main.java
Member findMember = em.find(Member.class, member.getId());
List<Member> members = findTeam.getTeam().getMembers(); // 팀 -> 회원 탐색
// Team에서 Member 접근 후 출력
for(Member m : members){
System.out.println("m = "+m.getName());
}
연관관계의 주인
양방향 매핑 규칙
- 객체의 두 관계 중 하나를 연관관계의 주인으로 지정
- 연관관계의 주인만이 외래 키를 관리(등록, 수정)
- 주인이 아닌 쪽은 읽기만 가능
- 주인은 mappedBy 속성 사용 X
- 주인이 아니면 mappedBy 속성으로 주인 지정
❓ mappedBy 속성은 왜 쓰일까
말그대로 누군가에 의해 map 되었다! 양방향 관계에서 내가 주인이 아니라는 의미!
사실상 양방향 연관관계는 서로 다른 단방향 연관관계 2개의 로직으로 묶어서 양방향인 것처럼 보이게 하는 것인데,
객체 연관관계 = 단방향 연관관계 2개로 이루어짐
- 회원 → 팀 : 연관관계 1개(단방향)
- 팀 → 회원 : 연관관계 1개(단방향)
테이블 연관관계 = 1개
- 회원 ↔️ 팀 : 연관관계 1개(양방향)
테이블은 외래 키 하나만으로 양방향 연관관계를 맺는다.
엔티티를 양방향 연관관계로 설정하면 객체의 참조는 둘인데 외래 키는 하나이다.
따라서 둘 사이에 차이가 발생하게 된다. 그렇다면 둘 중 어떤 관계를 사용해서 외래 키를 관리해야 할까?
이런 차이로 인해 JPA에서는 두 객체 연관관계 중 하나를 정해서 테이블의 외래키를 관리해야 하는데 이것을 연관관계의 주인이라 한다.
❓ 그렇다면, 주인 결정하는 기준은 무엇인가
외래 키가 있는 곳을 주인으로 정함!
아래의 경우 Member.team이 연관관계의 주인이 된다.
반대로 Team.members는 주인의 반대편 역할로 mappedBy="team" 과 같이 "team"필드로 주인에게 map되어 있음을 명시해주어야 한다.
(1:N 관계에서 N인 곳이 주인, 1인 곳이 주인의 반대편 권장)
Member가 주인일 때 Team이 바뀌었다면 Member에서 Team 정보를 수정하면 되지만,
만약 Team을 주인으로 설정한다면 Team에 어떤 행위(수정, 삽입)했을 때 매핑되어 있는 Member 객체 모두에게 수정(Update)행위가 따르기 마련이다.
양방향 매핑시 주의점
❌ 연관관계의 주인에 값을 입력하지 않음
Member member = new Member();
member.setName("member1");
em.persist(member);
Team team = new Team();
team.setName("TeamA");
team.getMembers().add(member);
em.persist(team);
연관관계의 주인이 Member에 있는 team이다.
Team의 members는 mappedBy로 연관되어 읽기 전용이기 때문에 JPA가 update나 insert할 때는 고려하지 않는다. (mappedBy는 가짜매핑)
연관관계의 주인만이 외래 키의 값을 변경할 수 있기 때문에 주인인 Member.team에는 아무 값도 입력되지 않은 것과 같다.
아래와 같이 양쪽 방향 모두 값을 입력하지 않으면 JPA를 사용하지 않는 순수한 객체 상태에서 심각한 문제가 발생할 수 있기 때문이다.
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);
em.flush();
em.clear();
Team findTeam = em.find(Team.class, team.getId());
List<Memer> members = findTeam.getMembers();
for(Member m : members)
System.out.println("m = " + m.getUsername());
/* 출력결과
m = member1
*/
🔅 여기서 주의해야 할 점!
(1)을 주석처리 한 것과 같이 team의 members 컬렉션에 아무것도 저장하지 않으면 1차 캐시에는 순수한 객체 그대로 저장될 것임. 따라서 Members를 출력했을 때 team과 연결되어 있는 객체를 찾을 수 없으므로 아무것도 출력되지 않는다.
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("member1");
member.setTeam(team);
// (1) team.getMembers().add(member);
em.persist(member);
// (2)em.flush();
// (3)em.clear();
Team findTeam = em.find(Team.class, team.getId()); // 위의 (1)(2)(3) 주석처리 하면 DB가 아닌 1차 캐시에 저장되어 있음
List<Memer> members = findTeam.getMembers(); // 따라서 findTeam()으로 정보 얻어올 수 없음
for(Member m : members)
System.out.println("m = " + m.getUsername());
✅ 연관관계 편의 메소드를 생성해서 실수를 방지하자!
member.setTeam()과 team.getMembers().add()를 한 번에 처리하자는 의미이다.
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")// 테이블 칼럼명
private String username;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
public void setTeam(Team team){
this.team = team;
team.getMembers().add(this); // 나 자신의 instance를 넣어줌
}
}
이로써 메소드를 원자적으로 활용하여 실수도 방지하고, member와 team을 한 번에 세팅 가능하다.
'JPA' 카테고리의 다른 글
[JPA] 다양한 연관관계 매핑 (0) | 2022.11.28 |
---|---|
[JPA] 연관관계 매핑 - 단방향 연관관계 (0) | 2022.08.07 |
[JPA] 엔티티 매핑 (0) | 2022.07.28 |
[JPA] 준영속 상태 (0) | 2022.07.18 |
[JPA] 플러시(Flush) (0) | 2022.07.18 |
댓글