Search

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

Tags
Study
JPA
Database
Last edited time
2025/07/13 13:15
2 more properties
Search
JPA의 사실과 오해 (5) 동작관점의 연관관계
Study
JPA
Database
JPA의 사실과 오해 (5) 동작관점의 연관관계
Study
JPA
Database

1. 연관관계

1.1. 정의

하나의 객체에서 다른 객체로 탐색할 수 있는 경로
객체 지향은 캡슐화. A → B 메세지 전달이 잦으니까 연관관계를 설정한 것
예) ScreeningMovie
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
복사
ScreeningMovie를 참조 (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
복사