
지난 시간에는 연관관계 매핑 기초를 다루어보았습니다.
지난 시간에 배웠듯이
매핑 시에는 1. 다중성, 2. 단방향, 양방향, 3. 연관관계의 주인을 고려하여야 합니다.
✅ 다중성
- 다대일(@ManyToOne), 일대다(@OneToMay), 일대일(@OneToOne), 다대다(@ManyToMany)
- 보통은 다대일, 일대다 관계를 사용
✅ 단방향, 양방향
- 테이블은 항상 양방향이고 객체는 참조용 필드를 이용하므로 단방향(한쪽만 참조) / 양방향(양쪽이 서로 참조)이 존재
✅연관관계의 주인
- 객체를 양방향 매핑 시에 관리하는 곳이 두 곳이므로 데이터베이스를 관리하는 곳을 지정해야 함(외래키를 가진 곳으로 보통 지정)
- 연관관계 주인은 mappedBy 속성을 사용하지 않음
(앞으로 살펴볼 연관관계는 왼쪽이 연관관계의 주인임을 기준으로 서술)
6.1 다대일
"양방향 다대일 관계에서 외래 키는 항상 다쪽 에 존재하므로 다가 연관관계의 주인이다"
6.1.1 다대일 단방향
회원엔티티와 팀 엔티티를 통해 다대일 단방향 연관관계를 알아봅시다.
@Entity
public class Member{
@Id @GeneratedValue
@Column(name="MEMBER_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;
...
}
- 회원은 Member.team으로 팀 엔티티를 참조할 수 있으나, 팀에는 회원을 참조하는 필드가 없으므로
다대일 단방향 연관관계입니다.
6.1.2 다대일 양방향
- 연관관계를 그릴 때, 실선 -> 연관관계의 주인 / 점선 -> 연관관계의 주인 아님
@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;
}
// 무한 루프에 빠지지 않도록
if(!team.getMembers().contain(this)){
team.getMembers().add(this);
}
}
@Entity
public class Team {
@Id @GeneratedValue
@Column(name="TEAM_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<Member>();
public void addMember(Member member){
this.members.add(member);
if(member.getTeam() != this){
member.setTeam(this);
}
}
✅ 양방향은 외래 키가 있는 쪽이 연관관계의 주인
다대일에서는 '다'에 외래키가 있어 다 쪽이 연관관계 주인이므로 여기서는 Member.team이 연관관계 주인입니다.
Team.members는 조회를 위한 JPQL, 객체 그래프 탐색 시에 사용됩니다.
✅양방향 연관관계는 항상 서로를 참조
항상 서로를 참조하게 하려면 연관관계 편의 메소드를 작성하는 게 좋고 여기서는 setTeam, addMember가 이런 메소드입니다.
편의 메소드는 한 쪽에만 작성해야 무한루프를 방지할 수 있습니다. 여기서는 setTeam, addMember 중 하나만 사용하면 됩니다.
6.2 일대다
6.2.1 일대다 단방향
이 때는 팀 엔티티가 Team.members로 회원 테이블의 TEAM_ID 외래 키를 관리하게 됩니다.
이 점이 굉장히 특이한 점인데요...!
왜냐면 보통은 자신이 매핑한 테이블의 외래키를 관리하는데 여기서는 자신이 매핑한 테이블의 외래 키가 아닌 반대 쪽 테이블의 외래키를 관리하기 때문입니다!
@Entity
public class Team {
@Id @GeneratedValue
@Column(name="TEAM_ID")
private Long id;
private String name;
@OneToMany
@JoinColumn(name- "TEAM_ID")
private List<Member> members = new ArrayList<Member>();
...
}
@Entity
public class Member {
@Id @GeneratedValue
@Column(name="MEMBER_ID")
private Long id;
private String username;
...
}
@JoinColumn을 명시하여야 합니다.
그렇지 않다면 JPA는 조인테이블 전략을 사용하여 매핑하기 때문입니다!
일대다 단방향 매핑의 단점
매핑한 객체가 관리하는 외래 키가 다른 테이블에 있기 때문에 연관관계 처리를 위해 UPDATE SQL을 추가로 실행해야 합니다.
Member 엔티티의 경우에는 Team 엔티티를 알 수 없고 연관관계의 정보는 Team.members가 관리하기 때문에
멤버 엔티티 저장 시에는 MEMBER 테이블에 TEAM_ID 외래 키에 아무 값도 저장되지 않기 때문에
Team 엔티티 저장 시 Team.members의 참조 값을 확인해 회원 테이블의 TEAM_ID 외래 키를 업데이트하게 됩니다.
✅ 일대다 단방향 매핑보다는 다대일 양방향 매핑 사용
앞서 살펴봤듯이 일대다 단방향 매핑 시에는 자신이 가지고 있지 않는 외래키를 관리해야 하므로 성능/관리 부분에서 좋지 않습니다!
그러므로 다대일 양방향 매핑을 사용하는 것이 좋겠죠?
6.2.2 일대다 양방향
일대다 양방향은 존재하지않으므로 다대일 양방향 매핑을 사용해야 합니다.
물론, 같은 외래 키를 사용하는 다대일 단방향 매핑을 하나 더 만들어서 사용하게 되면 구현 자체는 가능하지만
같은 테이블을 하나 더 만들어서 관리해야할 테이블을 만들게 되는 게 좋지 않으므로 다대일 양방향 매핑을 사용하면 됩니다.
6.3 일대일
일대일 관계는 양쪽이 하나의 관계만 가지게 됩니다.
- 내가 일대일 관계면, 다른 쪽도 일대일
- 일대일 관계에서는 둘 중 어느 곳이나 외래키를 가질 수 있음 -> 누가 외래 키를 가질 지 결정 필요!
✅주 테이블에 외래키
- 외래키를 객체 참조처럼 사용할 수 있어 객체 지향
- 주 테이블만 확인해도 대상 테이블과 연관관계를 알 수 있음
✅대상 테이블에 외래 키
- 테이블 관계를 일대일에서 일대다로 변경 시 테이블 구조 그대로 유지 가능
6.3.1 주 테이블에 외래 키
단방향
회원과 사물함의 일대일 단방향 관계를 통해 알아봅시다.
회원이 주테이블이고 사물함이 대상 테이블입니다.
@Entity
public class Member{
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
...
}
@Entity
public class Locker{
@Id @GeneratedValue
@Column(name = "LOCKER_ID")
private Long id;
priavte String name;
...
}
양방향
@Entity
public class Member{
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
...
}
@Entity
public class Locker{
@Id @GeneratedValue
@Column(name = "LOCKER_ID")
private Long id;
priavte String name;
@OneToOne(mappedBy = "locker")
private Member member;
}
...
}
MEMBER 테이블이 외래 키를 가지고 있어서 Member.locker가 연관관계의 주인이 되고
Locker.member에서는 mappedBy를 선언해서 연관관계의 주인이 아님을 설정했습니다.
6.3.2 대상 테이블에 외래 키
단방향
일대일 관계 중 대상 테이블에 외래 키가 있는 단방향 관계는 JPA에서 지우너하지 않으므로
단방향 관계의 방향을 수정하거나 양방향 관계로 만들어야 합니다.
양방향
@Entity
public class Member{
@Id @GeneratedValue
@Column (name = "MEMBER_ID")
private Long id;
private String username;
@OneToOne(mappedBy = "member")
private Locker locker;
...
}
@Entity
public class Locker{
@Id @GeneratedValue
@Column(name="LOCKER_ID")
private Long id;
private String name;
@OneToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;
...
}
6.4 다대다
관계형 데이터 베이스에서는 테이블 2개로 다대다를 표현할 수 없어서
다대다를 다대일, 일대다로 만들어주는 연결테이블을 사용하게 됩니다.
회원들이 상품을 주문하고
상품들이 회원들에 의해 주문되므로 다대다이고
이를 연결할 연결테이블로 member_product를 만들어주겠습니다.
객체의 경우에는 @ManyToMany를 사용해서 다대다 관계를 매핑할 수 있습니다.
6.4.1 다대다 : 단방향
@Entity
public class Member {
@Id @Column(name = "MEMBER_ID")
private String id;
private String username;
@ManyToMany
@JoinTable(name = "MEMBER_PRODUCT", joinColumns = @JoinColumn(name = "MEMBER_ID"),
inverseJoinColumns = @JoinColumn(name = "PRODUCT_ID"))
private List<Product> products = new ArrayList<Product>();
...
}
@Entity
public class Product {
@Id @Column(name = "PRODUCT_ID")
private String id;
private String name;
...
}
@ManyToMany와 @JoinTable을 사용해 연결 테이블을 바로 매핑하였습니다.
✅JoinTable의 속성
- name : 연결 테이블 지정
- joinColumns : 현재 방향인 회원과 매핑할 조인 컬럼 정보를 지정
- inverseJoinColumns : 반대 방향인 상품과 매핑할 조인 컬럼 정보 지정
6.4.2 다대다 : 양방향
양방향 이므로 역방향에도 @ManyToMany를 사용하고 연관관계의 주인을 지정하게 됩니다.
6.4.3 다대다 : 매핑의 한계와 극복. 연결 엔티티 사용
연결테이블은 단순히 회원 아이디, 상품 아이디만 담지 않을 수 있고 다른 컬럼이 추가되어야 하는 상황이 올 수 있습니다.
이렇게 컬럼을 추가하면 앞서 살펴본 @ManyToMany를 사용할 수 없습니다.
그러믄로 연결 엔티티를 만드는 과정이 필요합니다.
@Entity
public class Member{
@Id @Column(name = "MEMBER_ID")
private String id;
// 역방향
@OneToMany(mappedBy = "member")
private List<MemberProduct> memberProducts;
}
그런 다음, 회원 상품 엔티티와 회원상품 식별자를 클래스를 만ㄷ르게 됩니다.
회원 상품 엔티티에서는 기본 키와 외래키를 한번에 매핑하고 @IdClass를 사용해 복합 기본키로 매핑하게 됩니다.
✅복합 기본키
복합 키 사용시에는 별도의 식별자 믈래스를 만들고 엔티티에 @IdClass로 지정하게 됩니다.
식별자 클래스의 특징
- 복합 키는 별도의 식별자 클래스로 만들어야 함
- Serializable 구현
- equals, hashcode 구현
- 기본 생성자 있어야 함
- 식별자 클래스 public
- @IdClass 사용 방법 외에 @EmbeddedId 사용 가능
✅식별 관계
: 부모 테이블의 기본 키를 받아 자신의 기본 키 + 외래 키로 사용하는 것
복합 키를 사용하면 ORM 매핑에 관하여 처리할 일 많아짐, 식별자 클래스 만들어야 됨 ... 등 발생
6.4.4 다대다 : 새로운 기본 키 사용
대리키 사용 시 복합 키 만들지 않고 간단히 매핑 완성 가능
회원 상품 -> 주문으로 변경 후 다시 대리키를 이용한 방식으로 변경해봅시다.
주문 엔티티만 대리키 추가하고 나머지는 변경사항 없음...
6.4.5 다대다 연관관계 정리
- 식별 관계 : 받아온 식별자를 기본 키 + 외래 키로 사용
- 비식별 관계 : 받아온 식별자를 외래 키로 사용하고 새로운 식별자 추가
'BE > 자바 ORM 표준 JPA 프로그래밍' 카테고리의 다른 글
[JPA STUDY] 7장. 고급 매핑 (0) | 2025.05.04 |
---|---|
[JPA STUDY] 5. 연관관계 매핑 기초 (0) | 2025.04.06 |
[JPA STUDY] 4장. 엔티티 매핑 (0) | 2025.03.30 |
[JPA STUDY] 03. 영속성 관리 (0) | 2025.03.22 |
[ JPA STUDY ] 2장.JPA 시작 (0) | 2025.03.15 |