[JPA] 5장 - 연관관계 매핑 기초
본 글은 도서 '자바 ORM 표준 JPA 프로그래밍'의 5장 요약 정리본입니다.
객체 : 참조(주소) 로 관계 맺고 / 테이블 : 외래 키로 관계 맺음. → 완전히 다름 !!
객체 연관관계와 테이블 연관관계를 매핑하는 일이 객체 관계 매핑에서 가장 어려운 부분.
객체의 참조와 테이블의 외래키를 매핑하는 것이 5장의 목표 !
⭐ 핵심 키워드
방향 : 단방향(한쪽만 참조) / 양방향(서로 모두 참조; 테이블은 모두 양방향 by 외래키)
다중성 : 다대일, 일대다, 일대일, 다대다
연관관계의 주인 : 객체를 양방향 연관관계로 만들면, 연관관계의 주인을 정해야함 !
✳️ 단방향 연관관계
1️⃣객체 연관관계
Member.team : 회원 객체가 팀 객체를 참조. (단방향)
member.getTeam()으로 회원의 팀 조회 가능. but, 팀에서 회원으로 접근하는 필드는 없다
*객체 그래프 탐색 ; A.getB() 이런식으로 참조를 사용해 연관관계를 탐색할 수 있다.
2️⃣ 테이블 연관관계
회원 테이블의 외래키 : Team_Id (양방향)
서로 조인 가능 ! member join team, team join member 둘 다 가능
*조인 ; 외래 키를 사용해 연관관계 탐색
SELECT *
FROM TableA A
LEFT JOIN TableB B ON A.id = B.a_id;
객체 연관관계와 테이블 연관관계의 차이점
- 참조는 언제나 필드를 사용한 단방향.
서로 필드를 추가해 참조를 보관해도, 양방향이 아니라 서로 다른 단방향 관계가 2개인 것. - 테이블은 외래 키 하나로 양방향 조인.
연관관계 매핑 어노테이션
@ManyToOne : 다대일 관계 매핑 (필수)
@JoinColumn(name="외래키이") : 외래 키 매핑 (생략가능)
✳️ 연관관계 사용
1️⃣ 저장
+ JPA에서 엔티티를 저장할 때 연관된 모든 엔티티는 영속 상태여야 한다.
// 팀 1 저장
Team team1 = new Team("team1", "팀1");
em.persist(team1);
// 회원1 저장
Member member1 = new Member("member1", "회원1");
member1.setTeam(team1); // 연관관계 설정 ; 회원->팀 참조
em.persist (member1); // 저장
JPA는 참조한 팀의 식별자를 외래 키로 사용해 등록 쿼리 자동 생성함 !
2️⃣ 조회
1. 객체 그래프 탐색 (객체 연관관계 사용)
B.getA() 이런식으로 객체를 통해 연관된 엔티티를 조회
2. 객체지향 쿼리 사용 (JPQL)
String jpql = "select m from Member m join m.team t where " +
"t.name =:teamName";
List<Member> resultList = em.createQuery(jpql, Member.class)
.setParameter("teamName", "팀1")
.getResultList();
3️⃣ 수정
Team team2 = new Team("team2", "팀2");
em.persist(team2);
Member member = em.find(Member.class, "member1");
member.setTeam(team2); //참조하는 team1을 team2로 변경
팀1 소속이던 회원을 새로 팀2에 소속하도록 수정.
4️⃣ 연관관계 제거
Member member1 = em.find(Member.class, "member1");
member1.setTeam(null); // 연관관계 제거
연관관계 null로 설정해서 제거.
+ 외래 키 제약조건으로 DB오류가 발생하니, 연관된 엔티티를 삭제하려면 연관관계를 먼저 제거해야함 !
✳️ 양방향 연관관계
양방향 매핑하기
// 추가
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<Member>();
팀 엔티티 수정 ! ( Team 엔티티에 속한 Member 객체들의 리스트를 저장하는 필드 추가 )
✳️ 연관관계의 주인 - mappedBy
외래 키 관리자를 선택하는 것.
연관관계의 주인만이 DB 연관관계와 매핑되고, 외래 키를 관리할 수 있다. (주인 아닌 쪽은 읽기만 가능)
테이블은 외래 키 하나로 두 테이블의 연관관계를 관리하지만,
엔티티를 양방향 연관관계로 설정하면, 객체의 참조는 둘, 테이블 외래 키는 하나라는 차이가 발생.
따라서 두 객체 연관관계 중 하나를 정해 테이블의 외래 키를 관리하도록 함 → 연관관계의 주인
테이블에 외래 키가 있는 곳으로 연관관계의 주인을 정해야한다.
다대일, 일대다는 항상 다 쪽이 외래 키 가짐. @MantToOne은 mappBy 설정할 필요 없으므로 속성 존재 x
✳️ 양방향 연관관계 저장
team1.getMembers().add(member1); //무시(연관관계의 주인이 아님)
team1.getMembers().add(member2); //무시(연관관계의 주인이 아님)
*add() : 리스트에 객체를 추가
연관관계의 주인인 Member.team이 다음과 같이 외래 키를 관리.
member1.setTeam(team1); //연관관계 설정(연관관계의 주인)
member2.setTeam(team1); //연관관계 설정(연관관계의 주인)
✳️ 양방향 연관관계의 주의점
team1.getMembers().add(member1); //무시(연관관계의 주인이 아님)
team1.getMembers().add(member2); //무시(연관관계의 주인이 아님)
//member1.setTeam(team1); //연관관계 설정(연관관계의 주인)
//member2.setTeam(team1); //연관관계 설정(연관관계의 주인)
연관관계의 주인이 아닌 곳(예: Team.members)에만 값을 넣으면, 외래 키가 null로 설정될 수 있다.
객체 관점에서 team1.getMembers().size()는 올바르게 값을 반환하지 않음
따라서 양쪽 모두에 값을 설정하는 것이 중요 `
연관관계 편의 메서드
이처럼 양방향 연관관계는 양쪽 다 신경을 써야 하는데 각각 코드를 호출하면 실수로 한 곳에만 값을 넣어줄 수도
이를 방지하기 위해 다음과 같이 set을 수정하면 좋음
public class Member {
private Team team;
public void setTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
...
public void test() {
Team team1 = new Team("team1", "팀1");
em.persist(team1);
Member member1 = new Member("member1", "회원1");
member1.setTeam(team1); // 양방향 설정
em.persist(member1);
Member member2 = new Member("member2", "회원2");
member2.setTeam(team1); // 양방향 설정
em.persist(member2);
}
연관관계 편의 메서드 주의사항
member1의 Team을 수정했을 때 기존 팀에서 여전히 member1이 조회되는 문제.
member1.setTeam(teamA) //teamA
member1.setTeam(teamB) //teamA -> teamB 수정
Member findMember = teamA.getMember(); //member1이 여전히 조회된다.
연관관계를 수정할 때, 기존 연관관계를 제거하지 않아서 발생.
다음과 같이 수정.
public void setTeam(Team team) {
if (this.team != null) {
this.team.getMembers().remove(this);
}
this.team = team;
team.getMembers().add(this);