[JPA] @ManyToOne, @OneToMany 연관관계 정리

2025. 11. 22. 19:30·Back-End/Spring

Spring Boot 프로젝트를 진행하다 보면 JPA Entity 간의 관계를 설정하기 위해 @ManyToOne, @OneToMany 같은 어노테이션을 자주 사용하게 된다.

 

처음에는 단순히 Entity끼리 연결하기 위해 사용했지만, 프로젝트를 진행할수록 연관관계의 방향, 주인, mappedBy, 지연 로딩 같은 개념을 한 번 정리할 필요가 있다고 느꼈다.

이번 글에서는 JPA 연관관계 중 가장 자주 사용되는 @ManyToOne과 @OneToMany를 중심으로 정리해보려 한다.

연관관계란?

JPA에서 연관관계는 Entity와 Entity 사이의 관계를 객체로 표현하는 방식이다.

예를 들어 하나의 회원이 여러 개의 상품을 등록할 수 있다고 해보자.

회원 1명 → 상품 여러 개

DB 테이블에서는 보통 product 테이블이 member_id 외래 키를 가진다.

member
- id
- name

product
- id
- name
- member_id

객체에서는 이를 다음과 같이 표현할 수 있다.

@Entity
@Getter
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member;
}

여기서 Product는 하나의 Member를 참조한다.

즉, 상품 입장에서는 여러 상품이 하나의 회원을 바라볼 수 있으므로 @ManyToOne 관계가 된다.

@ManyToOne

@ManyToOne은 여러 Entity가 하나의 Entity를 참조하는 관계다.

여러 상품 → 한 명의 회원

상품과 회원 관계에서는 상품이 Many, 회원이 One이다.

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;

@JoinColumn은 외래 키 컬럼을 지정할 때 사용한다.

위 코드에서는 product 테이블의 member_id 컬럼이 member 테이블을 참조한다.

product.member_id → member.id

보통 외래 키는 Many 쪽 테이블에 존재한다.

따라서 @ManyToOne이 붙은 Entity가 연관관계의 주인이 되는 경우가 많다.

@OneToMany

@OneToMany는 하나의 Entity가 여러 Entity를 참조하는 관계다.

회원 입장에서 보면 한 명의 회원은 여러 상품을 가질 수 있다.

한 명의 회원 → 여러 상품

이를 객체로 표현하면 다음과 같다.

@Entity
@Getter
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = "member")
    private List<Product> products = new ArrayList<>();
}

여기서 중요한 부분은 mappedBy다.

@OneToMany(mappedBy = "member")

mappedBy = "member"는 Product Entity의 member 필드에 의해 매핑된 관계라는 의미다.

즉, Member의 products는 연관관계를 직접 관리하는 필드가 아니라, Product.member를 기준으로 조회되는 반대편 관계다.

연관관계의 주인

JPA에서 양방향 연관관계를 이해하려면 연관관계의 주인을 알아야 한다.

연관관계의 주인은 외래 키를 관리하는 쪽이다.

앞의 예시에서는 product 테이블이 member_id 외래 키를 가지고 있다.

따라서 연관관계의 주인은 Product.member다.

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;

반대로 Member.products는 조회를 위한 반대편 관계다.

@OneToMany(mappedBy = "member")
private List<Product> products = new ArrayList<>();

즉, 양방향 관계에서는 다음과 같이 이해할 수 있다.

Product.member
- 연관관계의 주인
- 외래 키 관리
- @JoinColumn 사용

Member.products
- 연관관계의 반대편
- mappedBy 사용
- 외래 키 직접 관리 X

단방향과 양방향

JPA 연관관계는 단방향으로도 설정할 수 있고, 양방향으로도 설정할 수 있다.

상품에서 회원만 필요하다면 Product → Member 단방향 관계만으로 충분하다.

@Entity
@Getter
public class Product {

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member;
}

하지만 회원을 조회했을 때 해당 회원이 등록한 상품 목록도 필요하다면 Member → Product 방향도 추가할 수 있다.

@OneToMany(mappedBy = "member")
private List<Product> products = new ArrayList<>();

여기서 중요한 점은 처음부터 무조건 양방향으로 만드는 것이 아니라는 점이다.

양방향 관계는 객체 탐색에는 편리하지만, 연관관계 관리와 조회 범위가 복잡해질 수 있다.

따라서 기본적으로는 단방향 관계를 먼저 고려하고, 반대 방향 조회가 실제로 필요할 때 양방향을 추가하는 편이 좋다.

FetchType은 LAZY를 기본으로 생각하자

연관관계를 설정할 때 함께 자주 등장하는 옵션이 fetch다.

@ManyToOne(fetch = FetchType.LAZY)
private Member member;

LAZY는 연관된 Entity를 실제로 사용할 때 조회하는 방식이다.

반대로 EAGER는 처음 Entity를 조회할 때 연관된 Entity까지 함께 조회하는 방식이다.

처음에는 EAGER가 편해 보일 수 있다.

하지만 필요하지 않은 데이터까지 항상 함께 조회될 수 있고, 예상하지 못한 쿼리가 발생할 수 있다.

따라서 실무에서는 연관관계를 기본적으로 LAZY로 설정하고, 필요한 경우 Fetch Join이나 DTO 조회로 필요한 데이터를 명확히 가져오는 방식이 많이 사용된다.

연관관계 편의 메서드

양방향 연관관계에서는 양쪽 객체 상태를 함께 맞춰주는 것이 좋다.

예를 들어 상품에 회원을 설정할 때, 회원의 상품 목록에도 함께 추가해줄 수 있다.

public void assignMember(Member member) {
    this.member = member;
    member.getProducts().add(this);
}

이런 메서드를 연관관계 편의 메서드라고 한다.

객체 관점에서는 Product.member와 Member.products가 함께 맞춰져야 자연스럽다.

다만 실제 외래 키는 연관관계의 주인인 Product.member가 관리한다는 점은 기억해야 한다.

주의할 점

JPA 연관관계를 사용할 때는 몇 가지 주의할 점이 있다.

1. 무조건 양방향 관계로 만들 필요는 없다.
2. 외래 키를 가진 쪽이 연관관계의 주인이다.
3. mappedBy는 연관관계의 반대편에 사용한다.
4. FetchType은 가능하면 LAZY를 기본으로 고려한다.
5. Entity를 그대로 응답하면 연관관계로 인해 N+1이나 순환 참조 문제가 발생할 수 있다.

특히 연관관계는 조회 성능과도 밀접하게 연결된다.

@ManyToOne, @OneToMany 자체를 이해하는 것도 중요하지만, 이후에는 N+1 문제, Fetch Join, DTO 조회까지 함께 이해해야 한다.

정리

JPA 연관관계는 DB의 외래 키 관계를 객체 참조로 표현하는 방식이다.

@ManyToOne은 여러 Entity가 하나의 Entity를 참조할 때 사용하고, @OneToMany는 하나의 Entity가 여러 Entity를 참조할 때 사용한다.

양방향 관계에서는 외래 키를 관리하는 쪽이 연관관계의 주인이며, 반대편에는 mappedBy를 사용한다.

처음부터 모든 관계를 양방향으로 만들기보다는, 실제 조회 방향과 요구사항에 맞게 단방향부터 고려하는 것이 좋다.

'Back-End > Spring' 카테고리의 다른 글

[JPA] N+1 문제란 무엇일까?  (0) 2025.12.02
@RequestParam, @ModelAttribute, @RequestBody는 언제 사용할까?  (0) 2025.11.24
[SpringBoot] Entity를 DTO로 변환해야 하는 이유  (0) 2025.11.22
ID값을 UUID v4에서 UUID v7로 변경한 이유  (0) 2025.11.21
UUID만으로 구분하기 어려운 데이터를 @PrePersist로 개선해보기  (0) 2025.11.11
'Back-End/Spring' 카테고리의 다른 글
  • [JPA] N+1 문제란 무엇일까?
  • @RequestParam, @ModelAttribute, @RequestBody는 언제 사용할까?
  • [SpringBoot] Entity를 DTO로 변환해야 하는 이유
  • ID값을 UUID v4에서 UUID v7로 변경한 이유
devoks
devoks
느려도 꾸준히
  • devoks
    ok's 개발일지
    devoks
  • 전체
    오늘
    어제
    • 분류 전체보기
      • Front-End
      • Back-End
        • Spring
        • Infra
        • AI
      • Computer Science
        • Cs
      • 언어
        • Java
        • SQL
      • 코테
        • Java
        • MySQL
      • Etc.
  • 블로그 메뉴

    • 홈
  • 링크

    • My GitHub
  • 공지사항

  • 인기 글

  • 태그

    programmers
    BufferedWriter
    replaceAll
    Regex
    역직렬화
    PaaS
    정규표현식
    switch
    effectivejava
    persist
    json
    BufferedReader
    stack
    StringTokenizer
    최대공약수
    PrePersist
    IaaS
    BFS
    dfs
    compare
    springboot
    java
    최대공배수
    replace
    docker
    Container
    CS
    CI/CD
    유클리드호제법
    codingtest
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
devoks
[JPA] @ManyToOne, @OneToMany 연관관계 정리
상단으로

티스토리툴바