컴공댕이 공부일지
[JPA] 6장 - 다양한 연관관계 매핑 본문
본 글은 도서 '자바 ORM 표준 JPA 프로그래밍'의 6장 요약 정리본입니다.
지난 내용 요약
엔티티의 연관관계를 매핑할 때 고려해야할 3가지
- 다중성
- 다대일, 일대다, 일대일, 다대다
- 단방향, 양방향
- 테이블은 외래 키 하나로 조인해서 모두 양방향 !
- 객체는 참조용 필드 가진 객체만 연관 객체 조회 가능
- 단방향 : 한 쪽만 참조, 양방향 : 양쪽이 서로 참조
- 연관관계의 주인
- 엔티티를 양방향으로 매핑 시, 2곳에서 서로를 참조하므로
JPA가 두 객체 연관관계 중 하나를 정해 데이터베이스 외래 키를 관리함. - 외래 키 가진 테이블과 매핑한 엔티티를 주로 연관관계의 주인으로 선택
(외래 키 관리하기 효율적이므로.) - 연관관계의 주인은 mappedBy 속성 사용 x
연관관계 주인 아니면 mappedBy 속성 사용하고 연관관계의 주인 필드 이름을 값으로 입력
- 엔티티를 양방향으로 매핑 시, 2곳에서 서로를 참조하므로
📖 다대일
다대일 : N이 1을 참조
외래키는 항상 다쪽에 존재.
→ 객체 양방향 관계에서 연관관계의 주인 또한 다쪽이다.
@ManyToOne 어노테이션을 사용하여 매핑
연관관계의 주인:
다(N) 쪽에 있는 엔티티가 연관관계의 주인이 되는 것이 일반적
@JoinColumn 어노테이션을 사용하여 외래 키를 매핑
다대일 단방향 [ N : 1 ]
하나의 엔티티에서만 다른 엔티티를 참조. 외래키 가진 엔티티에서 연관된 엔티티를 조회할 때 !
Member.team으로 팀 엔티티 참조&외래키 관리 ! 반대는 불가능 (단방향)
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID") //Member.team 필드를 TEAM_ID 외래키와 매핑
private Long id;
private String username;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
...
@Entity
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
...
다대일 양방향 [ N : 1, 1 : N ]
외래키가 있는 쪽이 연관관계의 주인!
Member.team이 연관관계의 주인.
양방향 연관관계는 항상 서로를 참조해야 함!
setTeam(), addMember() 등의 편의 메소드를 양쪽 혹은 한쪽에 작성해 서로 참조.
편의 메소드를 양쪽 다 작성할 땐 무한루프에 빠질 수 있으니 주의해야함
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
public void setTeam(Team team) {
this.team = team;
// 무한루프에 빠지지 않도록 체크 - 현재 멤버객체(this)가 해당 team에 포함되어있지 않다면 추가.
if(!team.getMembers().contains(this)) {
team.getMembers().add(this);
}
...
@Entity
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany(mappedby = "team") // 연관관계 주인이 Member !
private List<Member> members = new ArrayList<Member>();
public void addMember(Member member) {
this.members.add(member);
//무한 루프에 빠지지 않도록 체크 - this의 팀에 속하지 않았을 때, 멤버 추가
if(member.getTeam() != this) {
member.setTeam(this)
}
...
📖 일대다
일대다 : 1이 N을 참조
일대다 관계는 다대일의 반대방향
일대다 관계는 엔티티를 하나 이상 참조할 수 있으므로, (1이 다를 참조 !)
자바 컬렉션인 Collection, List, Set, Map 중 하나를 사용
일대다 단방향 [ 1:N ]
보통 자신이 매핑한 테이블의 외래 키를 관리하는데, 이 매핑은 반대쪽 테이블에 있는 외래 키를 관리.
@Entity
public class Team {
@Id @GeneneratedValue
@Column(name = "TEAM_ID")
private Long id;
@OneToMany
@JoinColumn(name = "TEAM_ID") // 명시적 외래키 지정 ; MEMBER 테이블의 TEAM_ID(FK)
private List<Member> members = new ArrayList<Member>();
...
}
@Entity
public class Member {
@Id @GeneneratedValue
@Column(name = "MEMBER_ID")
private Long id;
...
}
❓일대다 단방향 매핑의 단점
매핑한 객체가 관리하는 외래 키가 다른 테이블에 있다는 점.
본인 테이블에 외래 키가 있으면, 엔티티의 저장과 연관관계 처리를 INSERT SQL 한 번으로 끝낼 수 있지만
다른 테이블에 외래 키가 존재하므로, 연관관계 처리를 위한 UPDATE SQL을 추가로 실행해야함.
Team만이 Member를 참조하는 필드를 가지고 있으므로, Member를 insert로 저장하고, 해당 멤버가 속한 팀 정보를 업데이트하기 위해 update 진행해야함. 본인 테이블에 외래 키가 있으면 insert만으로 모든 처리가 끝남.
일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하자 !
앞서 언급했듯, 엔티티를 매핑한 테이블이 아닌 다른 테이블이 외래 키를 관리해야함. 성능 및 관리 차원에서 별로 !
다대일 양방향은 외래 키가 본인 테이블에 있어 관리가 쉽고, 두 매핑의 테이블 모양이 완전히 같아 엔티티만 약간 수정하면 된다. 상황에 따라 다르지만, 다대일 양방향 매핑을 보다 더 권장한다.
일대다 양방향 [ N : 1, 1 : N ]
사실 다대일 양방향과 동일함. 본 교재에서는 왼쪽(일)이 오른쪽(다)를 참조한다고 가정했음.
관계형 데이터베이스 특성상 일대다, 다대일 관계는 항상 다 쪽에 외래 키가 있음.
따라서 연관관계의 주인은 언제나 ManyToOne이므로, ManyToOne에는 mappedBy 속성이 없다.
그러나 아래와 같은 방식으로 일대다 양방향 매핑을 시도할 수 있다.
@Entity
public class Team {
@Id @GeneneratedValue
@Column(name = "TEAM_ID")
private Long id;
@OneToMany
@JoinColumn(name = "TEAM_ID") //MEMBER 테이블의 TEAM_ID(FK)
private List<Member> members = new ArrayList<Member>();
...
}
@Entity
public class Member {
@Id @GeneneratedValue
@Column(name = "MEMBER_ID")
private Long id;
//다대일 단방향 매핑 추가 (읽기전용)
@ManyToOne
@JoinColumn(name = "TEAM_ID", insertable = false, updatable = false)
private Team team;
...
}
‼️일대다 단방향 + 다대일 단방향 (읽기전용) → likes 일대다 양방향
두 연관관계 모두 같은 TEAM_ID 외래 키를 관리하므로 문제가 생길 수 있기에,
다대일 쪽에는 외래 키를 수정하지 못하도록 속성을 false로 두어 읽기만 가능하도록 했다.
위 방식은 일대다 양방향이라기보단, 일대다 단방향 매핑 반대편에 다대일 단방향 매핑을 읽기 전용으로 추가해서 일대다 양방향처럼 보이도록 하는 방법이다. 일대다 단방향 매핑이 가지는 단점을 그대로 가진다.(반대편 테이블의 외래키를 관리하는 것!)
결론 ! 가능한 다대일 양방향 매핑을 사용해 본인 테이블의 외래키를 본인이 관리하도록 할 것 ~
📖 일대일 [1:1]
두 엔티티가 정확히 하나씩 서로 연관되는 관계
일대일 관계는 그 반대도 일대일 관계 !
일대일 관계는 주 or 대상 테이블 둘 중 어느 곳이나 외래 키를 가질 수 있다.
(이전 관계들에서는 항상 다(N)쪽이 외래 키를 가짐)
주/대상 테이블 무관하게 외래 키 하나만 있으면 양쪽으로 조회 가능 + 일대일 관계는 반대쪽도 일대일 관계.
따라서 일대일 관계는 주/대상 중 누가 외래 키를 가질지 선택해야 함
- 주 테이블 : 참조되는 테이블
- 대상 테이블 : 주 테이블의 기본 키를 외래 키로 포함하는. 참조하는 테이블
1. 주 테이블에 외래 키
- 주 테이블에 외래 키를 두고 대상 테이블을 참조
- 주 테이블이 외래 키를 가지고 있으므로 주 테이블만 확인해도 대상 테이블과 연관관계가 있는지 알 수 있다.
단방향
@Entity
public class Member {
@Id @GeneneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
...
}
@Entity
public class Locker {
@Id @GeneneratedValue
@Column(name = "LOCKER_ID")
private Long id;
...
}
양방향
연관관계의 주인을 정해야 함 (Member.locker)
@Entity
public class Member {
@Id @GeneneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
...
}
@Entity
public class Locker {
@Id @GeneneratedValue
@Column(name = "LOCKER_ID")
private Long id;
//양방향 연관관계 매핑
@OneToOne(mappedBy = "locker")
private Member member;
...
}
2. 대상 테이블에 외래 키
- 대상 테이블에 외래 키를 두고 주 테이블을 참조
- 테이블 관계를 일대일에서 일대다로 변경할 때 테이블 구조를 그대로 유지할 수 있다.
단방향
→ 매핑 X
양방향
일대일 대상 테이블 외래키에 단방향은 JPA 지원 X
일대일 매핑에서 대상 테이블에 외래 키를 두고 싶으면 양방향으로 매핑한다.
- 주 테이블 단방향: 주 테이블에 외래 키를 두고 대상 테이블을 참조한다. @OneToOne과 @JoinColumn을 주 테이블 엔티티에 사용한다.
- 대상 테이블 단방향: 대상 테이블에 외래 키를 두고 주 테이블을 참조한다. @OneToOne과 @JoinColumn을 대상 테이블 엔티티에 사용한다.
- 양방향: 주 테이블 또는 대상 테이블 중 하나를 연관관계의 주인으로 설정하고, 다른 쪽은 mappedBy를 사용하여 주인을 참조한다. 주로 외래 키가 있는 테이블을 주인으로 설정한다. @OneToOne 어노테이션을 사용한다.
- 장점: 특정 엔티티에 대한 상세 정보를 별도의 테이블로 관리하여 테이블을 분리하고, 필요할 때만 조인하여 조회할 수 있다.
📖 다대다 [N:N]
- 여러 개의 엔티티가 여러 개의 다른 엔티티와 연관되는 관계.
- 관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다.
- 연결 테이블을 추가해서 일대다, 다대일 관계로 풀어내야만 한다.
- 매핑: 관계형 데이터베이스에서는 중간 테이블을 사용하여 다대다 관계를 표현한다. JPA에서는 @ManyToMany 어노테이션과 @JoinTable 어노테이션을 사용하여 중간 테이블을 매핑한다.
- @JoinTable: 중간 테이블의 이름, 조인할 컬럼 정보 등을 지정한다. joinColumns 속성에는 현재 엔티티를 매핑하는 외래 키 정보를, inverseJoinColumns 속성에는 반대쪽 엔티티를 매핑하는 외래 키 정보를 설정한다.
- 단방향: 하나의 엔티티에서만 다른 엔티티들의 컬렉션을 참조하는 방식.
- 양방향: 양쪽 엔티티 모두 서로의 컬렉션을 참조하는 방식. 한 쪽 엔티티에 @ManyToMany와 @JoinTable을 설정하고, 반대쪽 엔티티에는 @ManyToMany와 mappedBy를 사용하여 연관관계의 주인이 아님을 명시한다.
- 주의사항: 다대다 관계는 편리하지만, 중간 테이블에 추가적인 정보(예: 주문 시간, 수량 등)를 관리하기 어렵다는 단점이 있다. 이 경우에는 별도의 엔티티를 만들어 다대일-일대다 관계로 풀어내는 것을 고려해야 한다.
📖 정리
▼ 가능한 모든 연관관계들
- 다대일 : 단방향, 양방향 (왼쪽(다)을 연관관계의 주인으로)
- 일대다 : 단방향, 양방향
- 일대일 : 주 테이블 단방향, 양방향
- 일대일 : 대상 테이블 단방향, 양방향
- 다대다 : 단방향, 양방향
'study > 웹개발 스터디 백엔드 EFUB 💻' 카테고리의 다른 글
[JPA] 10장 - 객체지향 쿼리 언어(1) (3) | 2025.05.26 |
---|---|
[JPA] 5장 - 연관관계 매핑 기초 (0) | 2025.04.03 |
[JPA] 4장 - 엔티티 매핑 (0) | 2025.03.30 |
[JPA] 2장 - JPA 시작 (0) | 2025.03.16 |
[JPA] 1장 - JPA 소개 (0) | 2025.03.16 |