List
Search
1. 연관관계
1.1. 정의
•
하나의 객체에서 다른 객체로 탐색할 수 있는 경로
•
객체 지향은 캡슐화. A → B 메세지 전달이 잦으니까 연관관계를 설정한 것
◦
예) Screening 과 Movie
classDiagram class Screening { +reserve(customer, audienceCount) } class Movie { +calculateFee(screening) } Screening "1" -- "*" Movie : contains >
Mermaid
복사
1.2. 연관관계 고려사항
•
구조 관점
◦
외래키 맵핑
◦
다중성과 방향
•
동작 관점
◦
영속성 전이
◦
로딩
2. 구조관점의 연관관계
2.1. 외래키 맵핑
2.1.1. 정의
•
테이블의 외래키를 객체의 참조로 매핑
◦
예)
▪
Screening (N) - Movie (1)
▪
Screening 에서 FK (movieId)로 Movie를 참조
•
@JoinColumn 어노테이션
◦
객체 참조를 매핑하기 위해 사용할 FK 지정
@JoinColumn(name="MOVIE_ID")
private Movie movie;
Java
복사
2.1.2. 하나의 외래키, 두가지 방향
•
객체지향에서는 행위 관점에서 어느 방향으로 객체 참조할 지 결정하면 됨.
◦
FK가 있는 쪽에서 객체 참조를 하는 경우는 아무 문제 없음
◦
부모쪽에서 객체 참조를 갖고 있는 경우가 항상 문제 발생
•
다중성과 방향에 따라 @JoinColumn의 의미가 달라짐
◦
다중성
▪
1:1 / 1:N / N:1 / N:M
◦
방향
▪
단방향 / 양방향
◦
객체 지향은 양방향이 없음. 모든것이 단방향임
◦
양방향으로 가는 순간 복잡도가 올라감
2.1.3. @ManyToOne 매핑
•
Many가 무조건 FK를 물고 있으므로 Many쪽에서 One을 바라봄
•
참조가 한쪽에만 있기 때문에 단방향 연관관계
•
@JoinColumn(name = "MOVIE_ID")
◦
@ManyToOne 의 @JoiniColum 에는 클래스 자신이 매핑되는 테이블의 FK 지정
@Entity
public class Screening {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "MOVIE_ID")
private Movie movie;
private int sequence;
private LocalDateTime screeningTime;
}
@Entity
public class Screening {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private Money fee;
}
Java
복사
2.1.4. @OneToMany 매핑
•
@JoinColumn(name="MOVIE_ID")
•
자신이 매핑되지 않는 상대 테이블(Screening)의 FK (movie_id)를 지정
@Entity
public class Screening {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private int sequence;
private LocalDateTime screeningTime;
}
@Entity
public class Screening {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private Money fee;
@OneToMany
@JoinColumn(name="MOVIE_ID")
private set<Screening> screenings = new HashSet<>();
}
Java
복사
2.2. 다중성과 방향
2.2.1. FK의 소유 여부에 따른 동작방식
•
객체 참조가 FK를 직접 관리하는지 여부에 따라 엔티티(객체)와 DB 테이블 매핑 및 동작 방식이 달라짐
•
JPA/ORM에서 FK를 어느 쪽이 소유하느냐에 따라 연관관계 관리, 데이터 저장 방식이 다르게 동작함
◦
JPA에서는 연관관계의 주인(Owner이 반드시 필요
◦
DB 테이블 상 FK를 가진 테이블이, JPA에서도 연관관계의 주인이 됨
◦
연관관계의 주인이 아닌 쪽(mappedBy)은 DB FK를 관리하지 않고, 단순히 객체 그래프를 보조
•
예시)
◦
Screening 테이블의 MOVIE_ID가 외래키(FK)로 Movie를 참조
erDiagram SCREENING { bigint ID PK bigint MOVIE_ID FK int SEQUENCE datetime SCREENING_TIME } MOVIE { bigint ID PK string TITLE int RUNNING_TIME int FEE } SCREENING }o--|| MOVIE : "movie"
Mermaid
복사
•
Screening이 Movie를 참조
(FK 소유)
•
FK 매핑 + 객체 참조
classDiagram class Screening { +Long id +int sequence +LocalDateTime screeningTime +Movie movie } class Movie { +Long id +String title +int runningTime +int fee } Screening "*" -- "1" Movie : movie
Mermaid
복사
•
Screening에서 Movie 참조 (FK 소유)
•
Movie에서 Set<Screening> 참조 (연관관계의 주인이 아님)
classDiagram class Screening { +Long id +int sequence +LocalDateTime screeningTime +Movie movie } class Movie { +Long id +String title +int runningTime +int fee +Set~Screening~ screenings } Screening "*" -- "1" Movie : movie Movie "1" -- "*" Screening : screenings
Mermaid
복사
2.2.2. 양방향 관계 구현시 고려사항 (1) 객체 관점
•
두 객체 참조 싱크 간 맞추기
•
두 개의 단방향 연관관계를 싱크 맞추는 것
◦
관계 싱크를 직접적으로 코드레벨에서 해줘야함
◦
사실 싱크를 안맞춰도 업데이트는 잘됨
◦
근데 그건 DB 관점이고 객체 관점에서는 연관관계를 설정하는게 좋음
•
양방향 관계는 복잡함. 가급적이면 안하는게 좋음.
@Entity
public class Screening {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "MOVIE_ID")
private Movie movie;
private int sequence;
private LocalDateTime screeningTime;
public Screening(Movie movie, int sequence, LocalDateTime screeningTime) {
// movie 필드 할당
this.movie = movie;
// movie 객체에 Screening 추가 (양방향 설정)
this.movie.addScreening(this);
this.sequence = sequence;
this.screeningTime = screeningTime;
}
// Movie가 바뀔때도 양방향 설정 필요
public void changeMovie(Movie movie) {
if (movie != null) {
this.movie.remove(this);
}
this.movie = movie;
this.movie.addScreening(this);
}
}
Java
복사
@Entity
public class Movie {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private Money fee;
@OneToMany(mappedBy = "movie")
private List<Screening> screenings = new ArrayList<>();
// movie 객체에 Screening 추가 (양방향 설정)
public void addScreening(Screening screening) {
this.screenings.add(screening);
}
// Movie가 바뀔때도 양방향 설정 필요
public void remove(Screening screening) {
screenings.remove(screening);
}
}
Java
복사
2.2.3. 양방향 관계 구현시 고려사항 (2) DB 관점
•
두 객체 중에서 FK 매핑을 담당할 객체 선택
◦
객체 참조는 두개 이지만 FK는 하나
•
연관관계 주인
◦
두 객체 참조를 하나의 FK로 매핑할 책임을 지는 객체
◦
두 객체 중 어느 쪽에서 FK로의 매핑을 책임질 것인가?
•
연관관계 주인을 정하는 규칙
◦
연관관계 주인쪽(FK를 갖고 있는 객체)에 @JoinColumn 을 넣음
◦
연관관계 주인 반대쪽에서는 mappedBy 선언
•
기타 주의 사항
◦
연관관계 주인쪽에 참조가 설정되지 않으면 테이블에 외래키가 저장되지 않음
◦
영속성 전이 기능을 활용하면 연관객체가 함께 영속화 가능
@Entity
public class Screening {
@ManyToOne
@JoinColumn(name = "MOVIE_ID")
private Movie movie;
// ... 생략
}
@Entity
public class Movie {
@OneToMany(mappedBy = "movie")
private List<Screening> screenings = new ArrayList<>();
// ... 생략
}
Java
복사
2.2.4. M:N 관계
•
@JoinTable 활용
•
FK를 포함하는 ManyToOne쪽에 @JoinColumn 지정
•
OneToMany쪽에 @JoinTable 선언
erDiagram SCREENING { bigint ID PK bigint MOVIE_ID FK int SEQUENCE datetime SCREENING_TIME } MOVIE { bigint ID PK string TITLE int RUNNING_TIME int FEE } SCHEDULE { bigint MOVIE_ID PK bigint SCREENING_ID PK %% FK: MOVIE_ID, SCREENING_ID } SCREENING ||--o{ SCHEDULE : "" MOVIE ||--o{ SCHEDULE : ""
Mermaid
복사
@Entity
public class Screening {
@ManyToOne
@JoinColumn(name = "MOVIE_ID")
private Movie movie;
// ... 생략
}
@Entity
public class Movie {
@OneToMany
@JoinTable(
name = "SCHEDULE",
joinColumns = @JoinColumn(name = "SCREENING_ID"),
inverseJoinColumns = @JoinColumn(name = "MOVIE_ID")
)
private List<Screening> screenings = new ArrayList<>();
// ... 생략
}
Java
복사
2.2.5. 단방향 @OneToOne 매핑
•
@ManyToOne 때와 똑같음
•
외래키를 보유한 연관관계 주인쪽에 @OneToOne 매핑 선언
•
@OneToOne은 외래키가 없는 쪽에서는 매핑 불가
◦
외래키를 보유하지 않은 쪽에서는 연관관계 보유 불가
@Entity
public class Screening {
@OneToOne
@JoinColumn(name = "MOVIE_ID")
private Movie movie;
// ... 생략
}
@Entity
public class Movie {
// ... 생략
}
Java
복사
•
외래키가 없는 쪽에서 참조가 필요한 경우?
◦
양방향 @OneToOne 매핑 필요
◦
FK를 포함하는 쪽에서는 @JoinColumn 지정
◦
FK를 포함하지 않는 쪽에는 mappedBy 설정
@Entity
public class Screening {
@OneToOne
@JoinColumn(name = "MOVIE_ID")
private Movie movie;
// ... 생략
}
@Entity
public class Movie {
@OneToOne(mappedBy = "movie")
private Screening screening;
// ... 생략
}
Java
복사