Search
moon
sun

JPA의 사실과 오해 (4) 구조관점의 연관관계

Tags
Study
JPA
Database
Last edited time
2025/07/13 13:15
2 more properties
Search
JPA의 사실과 오해 (9) 동시성 제어 / 애그리거트 완성하기
Study
JPA
Database
JPA의 사실과 오해 (9) 동시성 제어 / 애그리거트 완성하기
Study
JPA
Database

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
복사