6. 연관관계 매핑 - 다중성
2025. 6. 16. 15:06ㆍSpring/JPA
1. 다대일 연관관계 [@ManyToOne]
1) 다대일 단방향
2) 다대일 양방향
2. 일대다 연관관계 [@OneToMany]
1) 일대다 단방향
2) 일대다 양방향
3. 일대일 연관관계 [@OneToOne]
1) 일대일 연관관계
2) 일대일 연관관계 구현
4. 다대다 연관관계 [@ManyToMany]
1) 이론적 구조
2) 다대다 연결관계의 한계
1. 다대일 연관관계 [@ManyToOne]
1) 다대일 단방향
@Entity
public class Member {
@ManyToOne
@JoinColumn(name = "TEAM_ID")
// Member 테이블에 있는 외래키 "TEAM_ID"를 Member.team 필드로 관리
private Team team;
}
2) 다대일 양방향
@Entity
public class Member {
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
}
@Entity
public class Team {
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
3) @ManyToOne 주요 속성
속성 | 설명 | 기본값 |
optional | false이면 항상 값 있어야 함 | true |
fetch | Fetch 전략 설정 | EAGER |
cascade | 영속성 전이 설정 | - |
targetEntity | 연관 타입 수동 설정 | - (대부분 생략) |
4) @OneToMany 주요 속성
속성 | 설명 | 기본값 |
mappedBy | 연관관계의 주인 필드 지정 | - |
fetch | Fetch 전략 설정 | LAZY |
cascade, targetEntity | 위와 동일 | - |
5) @JoinColumn 주요 속성
속성 | 설명 | 기본값 |
name | 외래 키 이름 | 필드명 + '_' + 참조 테이블 PK |
referencedColumnName | 참조하는 컬럼명 | 참조 테이블의 PK |
foreignKey | DDL 시 외래 키 제약 조건 명시 | 없음 |
unique, nullable, insertable, updatable 등 | @Column과 동일한 속성 사용 가능 | - |
2. 일대다 연관관계 [@OneToMany]
1) 일대다 단방향
@Entity
public class Team {
@OneToMany
@JoinColumn(name = "TEAM_ID")
// Member 테이블에 있는 외래키 "TEAM_ID"를 Team.members 필드로 관리
private List<Members> members = new ArrayList<>();
}
- 일(1)이 연관관계의 주인 (비자연적 구조)
- 테이블 외래 키는 항상 N쪽 테이블에 존재하기 때문에 Team INSERT 이후 Member UPDATE 쿼리가 발생
- 외래 키를 관리하는 테이블이 달라 비효율적이므로 추천되지 않음.
- 비효율적이고 잘 안 씀. 대신 일대다 단방향보다는 다대일 양방향을 사용하자
2) 일대다 양방향
@Entity
public class Member {
//ManyToOne(mappedBy = "members") → 이런거 없음!!
private Team team;
// mappedBy는 @ManyToOne에서는 사용불가, 항상 @OneToMany 쪽에만 사용해야 함!
// 따라서 이 필드는 연관관계에 포함되지 않음 (읽기용도 아니고, 그냥 무의미)
}
@Entity
public class Team {
@OneToMany
@JoinColumn(name = "TEAM_ID") // 외래키를 직접 Member 테이블에서 관리
private List<Members> members = new ArrayList<>();
}
- 일대다 양방향은 공식적으로 존재하지 않음
- 억지로 구현할 경우 읽기 전용 설정
@JoinColumn(insertable = false, updatable = false) - 일대다 양방향도 되도록 다대일 양방향으로 구성하는 것이 좋음
3. 일대일 연관관계 [@OneToOne]
1) 일대일 연관관계
- 하나의 엔티티가 오직 하나의 다른 엔티티와만 연결
- 회원 ↔ 회원 상세정보
- 주문 ↔ 배송정보
- Member ↔ Locker : 하나의 회원은 하나의 락커만 갖는 경우
- 일대일 연관관계는 외래 키를 어느 테이블에 둘까?
- 다대일 관계에선 다(Many) 테이블에 외래 키를 두어야 함
- 그런데 일대일 연관관계는 둘 중 아무 테이블에나 외래 키를 둘 수 있고, JPA도 개발자가 지정한 대로 따라간다.
- 단, 둘 중 한 테이블에만 외래 키가 있어야 일대일 성립 → 양쪽에 외래 키 두면 이상한 중복관계 됨!
- 보통은 외래키를 조회, 삽입, 수정이 더 자주 되는 쪽에 둔다
2) 일대일 연관관계 구현
- @OneToOne: 일대일 연관관계라는 걸 JPA에 알리는 어노테이션
- @JoinColumn: 이 엔티티가 외래키를 소유하게 된다
- 해당 필드로 외래키의 값을 수정하고 저장 가능
- 이 엔티티가 연관관계의 주인이 됨
- 실제 DB에 외래 키 컬럼이 생김
- 즉, 외래키가 있는 테이블 = 연관관계의 주인 엔티티가 매핑된 테이블
- mappedBy
- 양방향 연관관계인 경우사용
- 외래키를 사용하지 않는 반대쪽 엔티티에서 사용
- 이쪽은 DB에 아무 영향 안 주고 읽기 전용 관계임
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@OneToOne(mappedBy = "member") // ❌ 이 쪽엔 외래키 없음
private Locker locker;
}
@Entity
public class Locker {
@Id @GeneratedValue
private Long id;
private String location;
@OneToOne
@JoinColumn(name = "member_id") // ✅ 외래키 있음 (연관관계의 주인)
private Member member;
}
//반대로!
@Entity
public class Member {
@OneToOne
@JoinColumn(name = "locker_id") // ✅ 외래 키 소유자
private Locker locker;
}
@Entity
public class Locker {
@OneToOne(mappedBy = "locker") // ❌ 읽기 전용
private Member member;
}
4. 다대다 연관관계 [@ManyToMany]
1) 이론적 구조
- 객체에서는 컬렉션 사용으로 양방향 구성 가능
- 하지만 테이블에서는 연결 테이블을 추가하여 일대다, 다대일 관계로 풀어내야 한다.
2) 다대다 연결관계의 한계
- @ManyToMany 쓰면 JPA가 자동으로 중간 테이블 만들어주긴 하지만
- 중간 테이블을 별도로 다루기 어렵고 중간 테이블에 추가 컬럼을 못 넣음
- 실무에서는 단순 연결 외에도 수량, 주문시간 등 추가 정보 필요한데 중간 테이블에서는 구현 불가능
- 따라서 실전에서는 연결 테이블용 중간 엔티티 추가해서 다대다 관계를 해소해야한다.
@ManyToMany -> @OneToMany + @ManyToOne - 예를 들어 회원(Member)과 제품(Product) 테이블은 다대다 관계이므로
연결 테이블로 주문(Order) 테이블을 만들어서 해결한다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "member") //양방향 거울 역할
private List<Order> orders = new ArrayList<>();
}
//중간 엔티티
@Entity
public class Order {
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name="MEMBER_ID")
private Member member;
@ManyToOne
@JoinColumn(name="PRODUCT_ID")
private Product product;
}
@Entity
public class Product {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "product") //양방향 거울역할
private List<Order> orders = new ArrayList<>();
}