1. 개요
김영한 님의 '자바 ORM 표준 JPA 프로그래밍 - 기본편' 을 들으면서 정리하는 포스팅입니다.
https://www.inflearn.com/course/ORM-JPA-Basic
자바 ORM 표준 JPA 프로그래밍 - 기본편 강의 | 김영한 - 인프런
김영한 | JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., 실무에서도
www.inflearn.com
2. Entity 연관 관계 정의
JPA를 제대로 사용하기 위해서는 데이터베이스 테이블과 객체 간 매핑을 이해해야 한다. 이전 포스팅에서는 기본 매핑(테이블과 엔티티 간 1차원 적 매핑)에 대해 다루었고, 이번에는 엔티티 간의 연관관계를 정의하는 방법을 정리하겠다.
연관 관계를 정의하기 위해 다음 3가지 개념은 반드시 알아야 한다.
- 방향(Direction): 단방향, 양방향
- 연관관계의 주인(Owner): 양방향 연관관계에서의 관리 주인
- 다중성(Multiplicity): N:1(다대일), 1:N(일대다), 1:1(일대일), N:M(다대다)
3. 단방향/양방향 연관관계
엔티티의 단방향/양방향을 이해하기 전에 객체와 데이터베이스와 테이블의 차이를 알아야 한다.
a. 테이블과 객체의 차이
테이블 관점
- 외래키를 가지고 양쪽 테이블에서 조인이 가능하므로, 단방향/양방향을 나눌 필요가 없다.
- 예) Member, Team 관계에서 Member 테이블에 있는 team_id(외래키)를 통해 Member가 속한 Team을 조회할 수 있고, 반대로 Team 테이블에서 team_id를 통해 Team에 속한 Member들을 조회할 수 있다.
객체 관점
- 객체는 참조형 필드를 통해서 다른 객체를 참조해야 관계를 맺을 수 있다.
- 예) Member 객체 안에 Team 참조형 필드가 없으면 Member가 속한 Team 정보를 알 수 없고, 반대로 Team에서도 Member 참조형 필드가 없으면 Team에 속한 Member들을 확인할 수 없다.
b. 단방향 양방향 예시
따라서 객체 간 연관 관계를 설계할 때 단방향인지 양방향지 결정이 필요하다. 예를 들어 비즈니스적으로 Member 입장에서만 Team 정보가 필요하다면 단방향 관계로 정의하고, Team에서도 소속된 Member를 알아야 한다면 양방향 관계로 정의할 수 있을 것이다.
단방향(Member 입장에서만 Team 정보가 필요할 때)
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Getter private Team team;
}
member.getTeam(); // 팀 정보 조회
양방향(Team에서도 Member 정보가 필요할 때)
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Getter private Team team;
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
@Getter private List<Member> members = new ArrayList<>();
}
meber.getTeam(); // 팀 정보 조회
team.getMembers(); // 멤버들을 조회
c. 그럼 무조건 양방향 관계가 좋은거야?
엔티티 간의 연관관계는 무조건 양방향으로 설계하면 좋을 것 같지만, 실제로는 신중한 접근이 필요하다.
예를 들어, Member는 여러 엔티티와 연관 관계를 맺는 경우가 많다. 무조건 양방향으로 설계하면 Member 엔티티에 많은 참조형 필드가 추가되어 복잡성이 증가하며, 예기치 못한 쿼리가 발생할 가능성이 높아진다.
그러므로 무조건 적인 양방향 관계를 맺는 방법보다는 우선 단방향 관계만 맺어놓고 필요에 따라 양방향 관계로 맺어주는 것이 현명한 방법이 될 것이다.
4. 연관관계의 주인
- 양방향 관계일 때 연관 관계의 주인이 없으면 JPA는 어떤 엔티티를 기준으로 외래키를 관리해야 하는지 알 수 없다.
- 그러므로, 양방향 관계일 때는 연관 관계의 주인을 반드시 설정해줘야 한다.
- 일반적으로 연관관계의 주인은 외래키가 있는 쪽으로 설정한다.(Member 테이블에 team_id가 존재하면 Member가 연관관계의 주인)
- 연관관계의 주인은 READ, INSERT, UPDATE가 가능하다.
- 주인이 아닌 엔티티는 외래키에 영향을 주지 않음 => Read 전용으로 동작한다.
- mappedBy 속성을 통해 연관관계 주인을 설정한다.
a. 연관관계 설정 예시
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "team_id") // 연관관계 주인
private Team team;
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "team") // 읽기 전용(연관관계 주인 설정)
private List<Member> members = new ArrayList<>();
}
b. 연관관계의 주인만 제어하면 될까?
연관 관계의 주인만 제어하면 맞을까? 라는 질문에 정답은 NO이다.
DB 관점에서만 보면 연관관계의 주인 쪽에서만 변경하면 상관없다. 하지만 객체의 관계를 생각했을 때는 둘 다 변경해주는 것이 적절하다.
예를 들어 Member가 속한 Team을 변경한다고 가정하자.
단순 setter 메서드를 통해서 Team의 참조값만 변경했기 때문에 Team 입장에서는 members 컬렉션이 업데이트 되지 않는다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "team_id") // 연관관계 주인
@Setter
private Team team;
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "team") // 읽기 전용(연관관계 주인 설정)
private List<Member> members = new ArrayList<>();
}
Member member = em.find(Member.class, 1L);
Team oldTeam = member.getTeam();
Team newTeam = em.find(Team.class, 2L);
member.setTeam(newTeam); // Team 변경
oldTeam.getMembers(); // 불일치 문제 발생
따라서 Team의 members 컬렉션도 동기화가 필요하다. team을 변경할 때 setter 메서드 대신 연관관계 편의 메서드를 정의해서 사용하도록 하자.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
// 연관관계 편의 메서드
public void setTeam(Team team) {
if (this.team != null) {
this.team.getMembers().remove(this);
}
this.team = team;
if (team != null) {
team.getMembers().add(this);
}
}
}
5. 다중성
JPA에서는 연관관계를 정의할 때 N:1, 1:N, 1:1 N:M 4가지 다중성을 지원한다.
이를 위해 @ManyToOne, @OneToMany, @OneToOne, @ManyToMany와 같은 어노테이션을 사용한다.
Member와 Team의 관계로 예를 들어보자.
- 하나의 Team에는 여러명의 Member를 포함할 수 있다.
- 외래키는 Member 테이블에 team_id(외래키)가 존재한다.
- 이 관계를 데이터베이스에서는 Member(N) : Team(1) 관계로 표현한다.
a. N:1(다대일)
다대일 관계는 일반적으로 가장 많이 사용하는 연관관계이다.
외래키가 존재하는 곳이 연관관계의 주인이 된다.
Member(N)이 연관관계의 주인인 형태이다.
a-1. N:1 단방향
- 한 Member가 Team을 참조하며, 외래키는 Member 테이블에 존재한다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "team_id") // 외래 키 설정
private Team team;
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
}
a-2. N:1 양방향
- Member와 Team 모두 서로를 참조한다.
- 외래 키는 Member가 관리하며, Team은 mappedBy로 연관관계 주인 설정한다.
- Member(연관관계주인)에서는 쓰기 연산이 가능하지만, Team에서는 Read Only로 동작한다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
b. 1:N(1대다)
다대일 구조의 반대로 외래키가 없는 Team(1)이 연관관계의 주인이 되는 형태이다.
b-1. 1:N 단방향
- Team이 Member를 참조하며, Team(1)이 연관관계의 주인이다.
- @JoinColumn을 꼭 사용해야 한다. 사용하지 않으면 조인 테이블 방식을 사용하게 된다.
- 1:N 관계의 단점
- Post에서 새로운 Member를 추가하는데, Member에 대한 업데이트 쿼리가 발생한다.(예상하기가 어려움)
- 1:N 단방향을 사용하지 말고 N:1 양방향 매핑을 사용하도록 하자.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
@OneToMany
@JoinColumn(name = "team_id") // 외래 키 설정
private List<Member> members = new ArrayList<>();
}
Member member1 = new Member();
member1.setUsername("member1");
em.persist(member1); // Member Insert 쿼리
Team team1 = new Team();
team1.setName("team1");
team1.getMembers().add(member1);
em.persist(team1); // TEAM Insert 쿼리, Member Update 쿼리 발생
b-2. 1:N 양방향
- JPA에서 공식적으로 지원하는 구조는 아니다.
- 약간의 꼼수?를 써서 1:N 양방향 관계를 정의할 수 있다.
- 1:N 단방향 관계에서 Member(N) 쪽에서 Team을 참조하지만 쓰기 연산을 금지하도록 구현한다.
- N:1 관계가 있는데 굳이? 사용할 이유가 없어보인다...
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "team_id", insertable = false, update = false) // 쓰기 연산 금지
private Team team;
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
@OneToMany
@JoinColumn(name = "team_id") // 외래 키 설정
private List<Member> members = new ArrayList<>();
}
c. 1:1(1대1)
두 엔티티가 1:1 매핑 되는 구조이다.
주 테이블(Member)에 외래키를 넣을 수 있고, 대상 테이블(Locker)에 외래키를 넣을 수도 있다.
Member(멤버)와 Locker(락커) 예시를 보자.
c-1. 1:1 단방향
- Member가 Locker를 참조하고 있는 형태이다.
- N:1 단방향 관계와 유사하다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@OneToOne
@JoinColumn(name = "locker_id") // 외래 키 설정
private Locker locker;
}
@Entity
public class Locker {
@Id @GeneratedValue
private Long id;
}
c-2. 1:1 양방향
- 양쪽에서 서로를 참조하고 있는 형태이다.
- mappedBy로 연관관계의 주인을 설정해줘야 한다.(N:1 관계와 마찬가지로 외래키가 존재하는 곳이 연관관계 주인)
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@OneToOne
@JoinColumn(name = "locker_id")
private Locker locker;
}
@Entity
public class Locker {
@Id @GeneratedValue
private Long id;
@OneToOne(mappedBy = "locker")
private Member member;
}
d. N:M(다대다)
N:M 관계는 두 엔티티가 다대다로 매핑되는 구조이다. JPA에서 @ManyToMany를 통해 간단하게 설정할 수 있지만, 실무에서는 잘 사용하지 않는다. 사용하지 않는 이유를 예제를 통해 알아보자.
한 Member가 여러팀에 속할 수 있고, 한 팀에도 여러명이 속할 수 있다고 가정하자.
d-1. N:M 문제점
- 다대다는 중간테이블을 생성하는 방식이다. => 조인테이블
- 중간 테이블의 추가적인 컬럼(예: 생성날짜, 수정일 등)을 설정할 수 없고, Entity만 보고 쿼리를 예상하기가 쉽지 않다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@ManyToMany
@JoinTable(name = "member_team",
joinColumns = @JoinColumn(name = "member_id"),
inverseJoinColumns = @JoinColumn(name = "team_id"))
private List<Team> teams = new ArrayList<>();
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
@ManyToMany(mappedBy = "teams")
private List<Member> members = new ArrayList<>();
}
d-2. N:M 한계를 극복하는 방법
- 다대다 관계의 한계를 극복하려면 중간 테이블을 만들어 일대다 - 다대일로 풀어나가야 한다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "member")
private List<MemberTeam> memberTeams = new ArrayList<>();
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "team")
private List<MemberTeam> memberTeams = new ArrayList<>();
}
@Entity
public class MemberTeam {
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "member_id")
private Member member;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
}
'Programming > Java' 카테고리의 다른 글
[JPA] Entity 로딩 전략과 프록시(Proxy) (0) | 2024.12.23 |
---|---|
[JPA] JPA에서 DB 슈퍼-서브 타입 구현하는 방법 (0) | 2024.12.20 |
[JPA] JPA에서 PK를 다루는 방법(@Id, @GeneratedValue) (0) | 2024.12.12 |
[JPA] Entity 매핑과 DDL 자동 생성 옵션 정리 (0) | 2024.12.12 |
[JPA] JPA 영속성 컨텍스트와 Entity 상태(비영속, 영속, 준영속, 삭제) (1) | 2024.12.06 |
1. 개요
김영한 님의 '자바 ORM 표준 JPA 프로그래밍 - 기본편' 을 들으면서 정리하는 포스팅입니다.
https://www.inflearn.com/course/ORM-JPA-Basic
자바 ORM 표준 JPA 프로그래밍 - 기본편 강의 | 김영한 - 인프런
김영한 | JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., 실무에서도
www.inflearn.com
2. Entity 연관 관계 정의
JPA를 제대로 사용하기 위해서는 데이터베이스 테이블과 객체 간 매핑을 이해해야 한다. 이전 포스팅에서는 기본 매핑(테이블과 엔티티 간 1차원 적 매핑)에 대해 다루었고, 이번에는 엔티티 간의 연관관계를 정의하는 방법을 정리하겠다.
연관 관계를 정의하기 위해 다음 3가지 개념은 반드시 알아야 한다.
- 방향(Direction): 단방향, 양방향
- 연관관계의 주인(Owner): 양방향 연관관계에서의 관리 주인
- 다중성(Multiplicity): N:1(다대일), 1:N(일대다), 1:1(일대일), N:M(다대다)
3. 단방향/양방향 연관관계
엔티티의 단방향/양방향을 이해하기 전에 객체와 데이터베이스와 테이블의 차이를 알아야 한다.
a. 테이블과 객체의 차이
테이블 관점
- 외래키를 가지고 양쪽 테이블에서 조인이 가능하므로, 단방향/양방향을 나눌 필요가 없다.
- 예) Member, Team 관계에서 Member 테이블에 있는 team_id(외래키)를 통해 Member가 속한 Team을 조회할 수 있고, 반대로 Team 테이블에서 team_id를 통해 Team에 속한 Member들을 조회할 수 있다.
객체 관점
- 객체는 참조형 필드를 통해서 다른 객체를 참조해야 관계를 맺을 수 있다.
- 예) Member 객체 안에 Team 참조형 필드가 없으면 Member가 속한 Team 정보를 알 수 없고, 반대로 Team에서도 Member 참조형 필드가 없으면 Team에 속한 Member들을 확인할 수 없다.
b. 단방향 양방향 예시
따라서 객체 간 연관 관계를 설계할 때 단방향인지 양방향지 결정이 필요하다. 예를 들어 비즈니스적으로 Member 입장에서만 Team 정보가 필요하다면 단방향 관계로 정의하고, Team에서도 소속된 Member를 알아야 한다면 양방향 관계로 정의할 수 있을 것이다.
단방향(Member 입장에서만 Team 정보가 필요할 때)
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Getter private Team team;
}
member.getTeam(); // 팀 정보 조회
양방향(Team에서도 Member 정보가 필요할 때)
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Getter private Team team;
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
@Getter private List<Member> members = new ArrayList<>();
}
meber.getTeam(); // 팀 정보 조회
team.getMembers(); // 멤버들을 조회
c. 그럼 무조건 양방향 관계가 좋은거야?
엔티티 간의 연관관계는 무조건 양방향으로 설계하면 좋을 것 같지만, 실제로는 신중한 접근이 필요하다.
예를 들어, Member는 여러 엔티티와 연관 관계를 맺는 경우가 많다. 무조건 양방향으로 설계하면 Member 엔티티에 많은 참조형 필드가 추가되어 복잡성이 증가하며, 예기치 못한 쿼리가 발생할 가능성이 높아진다.
그러므로 무조건 적인 양방향 관계를 맺는 방법보다는 우선 단방향 관계만 맺어놓고 필요에 따라 양방향 관계로 맺어주는 것이 현명한 방법이 될 것이다.
4. 연관관계의 주인
- 양방향 관계일 때 연관 관계의 주인이 없으면 JPA는 어떤 엔티티를 기준으로 외래키를 관리해야 하는지 알 수 없다.
- 그러므로, 양방향 관계일 때는 연관 관계의 주인을 반드시 설정해줘야 한다.
- 일반적으로 연관관계의 주인은 외래키가 있는 쪽으로 설정한다.(Member 테이블에 team_id가 존재하면 Member가 연관관계의 주인)
- 연관관계의 주인은 READ, INSERT, UPDATE가 가능하다.
- 주인이 아닌 엔티티는 외래키에 영향을 주지 않음 => Read 전용으로 동작한다.
- mappedBy 속성을 통해 연관관계 주인을 설정한다.
a. 연관관계 설정 예시
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "team_id") // 연관관계 주인
private Team team;
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "team") // 읽기 전용(연관관계 주인 설정)
private List<Member> members = new ArrayList<>();
}
b. 연관관계의 주인만 제어하면 될까?
연관 관계의 주인만 제어하면 맞을까? 라는 질문에 정답은 NO이다.
DB 관점에서만 보면 연관관계의 주인 쪽에서만 변경하면 상관없다. 하지만 객체의 관계를 생각했을 때는 둘 다 변경해주는 것이 적절하다.
예를 들어 Member가 속한 Team을 변경한다고 가정하자.
단순 setter 메서드를 통해서 Team의 참조값만 변경했기 때문에 Team 입장에서는 members 컬렉션이 업데이트 되지 않는다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "team_id") // 연관관계 주인
@Setter
private Team team;
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "team") // 읽기 전용(연관관계 주인 설정)
private List<Member> members = new ArrayList<>();
}
Member member = em.find(Member.class, 1L);
Team oldTeam = member.getTeam();
Team newTeam = em.find(Team.class, 2L);
member.setTeam(newTeam); // Team 변경
oldTeam.getMembers(); // 불일치 문제 발생
따라서 Team의 members 컬렉션도 동기화가 필요하다. team을 변경할 때 setter 메서드 대신 연관관계 편의 메서드를 정의해서 사용하도록 하자.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
// 연관관계 편의 메서드
public void setTeam(Team team) {
if (this.team != null) {
this.team.getMembers().remove(this);
}
this.team = team;
if (team != null) {
team.getMembers().add(this);
}
}
}
5. 다중성
JPA에서는 연관관계를 정의할 때 N:1, 1:N, 1:1 N:M 4가지 다중성을 지원한다.
이를 위해 @ManyToOne, @OneToMany, @OneToOne, @ManyToMany와 같은 어노테이션을 사용한다.
Member와 Team의 관계로 예를 들어보자.
- 하나의 Team에는 여러명의 Member를 포함할 수 있다.
- 외래키는 Member 테이블에 team_id(외래키)가 존재한다.
- 이 관계를 데이터베이스에서는 Member(N) : Team(1) 관계로 표현한다.
a. N:1(다대일)
다대일 관계는 일반적으로 가장 많이 사용하는 연관관계이다.
외래키가 존재하는 곳이 연관관계의 주인이 된다.
Member(N)이 연관관계의 주인인 형태이다.
a-1. N:1 단방향
- 한 Member가 Team을 참조하며, 외래키는 Member 테이블에 존재한다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "team_id") // 외래 키 설정
private Team team;
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
}
a-2. N:1 양방향
- Member와 Team 모두 서로를 참조한다.
- 외래 키는 Member가 관리하며, Team은 mappedBy로 연관관계 주인 설정한다.
- Member(연관관계주인)에서는 쓰기 연산이 가능하지만, Team에서는 Read Only로 동작한다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
b. 1:N(1대다)
다대일 구조의 반대로 외래키가 없는 Team(1)이 연관관계의 주인이 되는 형태이다.
b-1. 1:N 단방향
- Team이 Member를 참조하며, Team(1)이 연관관계의 주인이다.
- @JoinColumn을 꼭 사용해야 한다. 사용하지 않으면 조인 테이블 방식을 사용하게 된다.
- 1:N 관계의 단점
- Post에서 새로운 Member를 추가하는데, Member에 대한 업데이트 쿼리가 발생한다.(예상하기가 어려움)
- 1:N 단방향을 사용하지 말고 N:1 양방향 매핑을 사용하도록 하자.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
@OneToMany
@JoinColumn(name = "team_id") // 외래 키 설정
private List<Member> members = new ArrayList<>();
}
Member member1 = new Member();
member1.setUsername("member1");
em.persist(member1); // Member Insert 쿼리
Team team1 = new Team();
team1.setName("team1");
team1.getMembers().add(member1);
em.persist(team1); // TEAM Insert 쿼리, Member Update 쿼리 발생
b-2. 1:N 양방향
- JPA에서 공식적으로 지원하는 구조는 아니다.
- 약간의 꼼수?를 써서 1:N 양방향 관계를 정의할 수 있다.
- 1:N 단방향 관계에서 Member(N) 쪽에서 Team을 참조하지만 쓰기 연산을 금지하도록 구현한다.
- N:1 관계가 있는데 굳이? 사용할 이유가 없어보인다...
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "team_id", insertable = false, update = false) // 쓰기 연산 금지
private Team team;
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
@OneToMany
@JoinColumn(name = "team_id") // 외래 키 설정
private List<Member> members = new ArrayList<>();
}
c. 1:1(1대1)
두 엔티티가 1:1 매핑 되는 구조이다.
주 테이블(Member)에 외래키를 넣을 수 있고, 대상 테이블(Locker)에 외래키를 넣을 수도 있다.
Member(멤버)와 Locker(락커) 예시를 보자.
c-1. 1:1 단방향
- Member가 Locker를 참조하고 있는 형태이다.
- N:1 단방향 관계와 유사하다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@OneToOne
@JoinColumn(name = "locker_id") // 외래 키 설정
private Locker locker;
}
@Entity
public class Locker {
@Id @GeneratedValue
private Long id;
}
c-2. 1:1 양방향
- 양쪽에서 서로를 참조하고 있는 형태이다.
- mappedBy로 연관관계의 주인을 설정해줘야 한다.(N:1 관계와 마찬가지로 외래키가 존재하는 곳이 연관관계 주인)
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@OneToOne
@JoinColumn(name = "locker_id")
private Locker locker;
}
@Entity
public class Locker {
@Id @GeneratedValue
private Long id;
@OneToOne(mappedBy = "locker")
private Member member;
}
d. N:M(다대다)
N:M 관계는 두 엔티티가 다대다로 매핑되는 구조이다. JPA에서 @ManyToMany를 통해 간단하게 설정할 수 있지만, 실무에서는 잘 사용하지 않는다. 사용하지 않는 이유를 예제를 통해 알아보자.
한 Member가 여러팀에 속할 수 있고, 한 팀에도 여러명이 속할 수 있다고 가정하자.
d-1. N:M 문제점
- 다대다는 중간테이블을 생성하는 방식이다. => 조인테이블
- 중간 테이블의 추가적인 컬럼(예: 생성날짜, 수정일 등)을 설정할 수 없고, Entity만 보고 쿼리를 예상하기가 쉽지 않다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@ManyToMany
@JoinTable(name = "member_team",
joinColumns = @JoinColumn(name = "member_id"),
inverseJoinColumns = @JoinColumn(name = "team_id"))
private List<Team> teams = new ArrayList<>();
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
@ManyToMany(mappedBy = "teams")
private List<Member> members = new ArrayList<>();
}
d-2. N:M 한계를 극복하는 방법
- 다대다 관계의 한계를 극복하려면 중간 테이블을 만들어 일대다 - 다대일로 풀어나가야 한다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "member")
private List<MemberTeam> memberTeams = new ArrayList<>();
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "team")
private List<MemberTeam> memberTeams = new ArrayList<>();
}
@Entity
public class MemberTeam {
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "member_id")
private Member member;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
}
'Programming > Java' 카테고리의 다른 글
[JPA] Entity 로딩 전략과 프록시(Proxy) (0) | 2024.12.23 |
---|---|
[JPA] JPA에서 DB 슈퍼-서브 타입 구현하는 방법 (0) | 2024.12.20 |
[JPA] JPA에서 PK를 다루는 방법(@Id, @GeneratedValue) (0) | 2024.12.12 |
[JPA] Entity 매핑과 DDL 자동 생성 옵션 정리 (0) | 2024.12.12 |
[JPA] JPA 영속성 컨텍스트와 Entity 상태(비영속, 영속, 준영속, 삭제) (1) | 2024.12.06 |