프레임워크와 라이브러리/JPA

[JPA 관계 매핑] 객체 간 관계를 데이터베이스에 자연스럽게 표현하는 법

Dachaes 2025. 4. 11. 14:14
728x90
반응형

JPA의 관계 매핑 

JPA는 객체 간의 연관 관계(association)를 데이터베이스의 외래 키 관계로 변환해주는 기능을 제공합니다. 객체지향에서는 필드 참조로 관계를 표현하고, 데이터베이스에서는 외래 키(Foreign Key)로 표현하죠.
이 둘을 매끄럽게 연결해주는 것이 바로 JPA의 관계 매핑(association mapping)입니다. 이 글에서는 1:1, 1:N, N:1, N:N 관계의 표현 방식과 주의사항, 실전 팁까지 모두 알아봅시다.

 


1.  왜 관계 매핑이 중요한가?

객체 지향 프로그래밍에서는 객체 간의 참조(Reference) 를 사용하지만, 관계형 데이터베이스에서는 외래 키(Foreign Key) 를 사용해 관계를 표현합니다.

즉, 다음과 같은 차이가 존재합니다.

객체 지향 세계 관계형 DB 세계
A → B (참조) A 테이블에 B의 외래 키
컬렉션 사용 가능 Join Table 필요

JPA는 이러한 차이를 해소하기 위해 다양한 관계 매핑 어노테이션을 제공합니다.

 

JPA의 관계 매핑 종류

관계 설명
@OneToOne 1:1 관계 (예: 사람 - 주민등록증)
@OneToMany 1:N 관계 (예: 팀 - 팀원들)
@ManyToOne N:1 관계 (예: 여러 주문 - 하나의 고객)
@ManyToMany N:N 관계 (예: 학생 - 강의, 서로 다수 참여)

 


2.  중심 관계 매핑

a.  @ManyToOne (N:1)

@Entity
public class Member {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @ManyToOne // Member : Team
    @JoinColumn(name = "team_id") // Foreign Key
    private Team team;
}
@Entity
public class Team {

    @Id
    @GeneratedValue
    private Long id;

    private String name;
}
  • Member 입장에서 Team은 N:1 관계
  • Member 테이블에 @JoinColumn으로 외래 키 명시
  • 연관관계의 주인은 Member (즉, 외래 키를 가진 쪽이 주인)

b.  @OneToMany (1:N)

i.  단방향 매핑

@Entity
public class Member {

    @Id
    @GeneratedValue
    private Long id;

    private String name;
}
@Entity
public class Team {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @OneToMany // Team : Member
    @JoinColumn(name = "team_id") // Member 테이블에 Foreign Key 생성
    private List<Member> members = new ArrayList<>();
}
  • 단방향 1:N 관계는 실제 DB에는 외래 키가 Member 테이블에 존재하므로, @JoinColumn을 반드시 명시해야 함
  • 잘 사용하지 않음. 대신 양방향 매핑 권장

ii.  양방향 매핑

@Entity
public class Team {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @OneToMany(mappedBy = "team") // Team : Member, Team 테이블은 연관관계의 주인이 아님
    private List<Member> members = new ArrayList<>();
}
  • mappedBy는 연관관계 주인이 아님을 표시
  • 주인은 외래 키를 가진 Member 쪽
  • 주인 쪽만 insert/update SQL을 발생시킴

c.  @OneToOne

@Entity
public class User {

    @Id @GeneratedValue
    private Long id;

    private String name;

    @OneToOne // User : Card
    @JoinColumn(name = "card_id")
    private Card card;
}
  • 드물게 사용되며, 외래 키 위치에 따라 mappedBy를 사용해 양방향 가능

d.  @ManyToMany (실무에서는 지양)

@Entity
public class Student {

    @Id @GeneratedValue
    private Long id;

    private String name;

    @ManyToMany
    @JoinTable(name = "student_course",
               joinColumns = @JoinColumn(name = "student_id"),
               inverseJoinColumns = @JoinColumn(name = "course_id"))
    private List<Course> courses = new ArrayList<>();
}
 
  • JPA는 중간 테이블을 자동 생성하지만, 실무에서는 중간 엔티티를 따로 만들어 @ManyToOne으로 쪼개는 것이 일반적

지양하는 이유

  1. 중간 테이블에 데이터를 추가로 넣을 수 없음
    • 예: 학생이 어떤 강의를 수강했는지 기록할 때 수강일, 성적, 출결 등 부가 정보를 넣고 싶을 수 있어요.
    • @ManyToMany는 중간 테이블에 컬럼 추가가 불가능합니다.
    • 그래서 보통 중간 테이블을 별도 엔티티로 만들고, @ManyToOne 관계로 나누는 구조를 사용합니다.
  2. 연관관계 관리가 어렵고 불명확함
    • @ManyToMany는 자동으로 중간 테이블을 관리하지만, 양방향일 경우 어느 쪽이 주인인지 헷갈릴 수 있고, set, add 같은 로직을 명확히 짜기 어려움
    • JPA 내부 동작을 정확히 이해하지 않으면 예측 불가한 동작이 나올 수 있음
  3. 성능 최적화/쿼리 튜닝이 어려움
    • 중간 테이블이 숨겨져 있어서 SQL 튜닝, 페이징, 조건 검색 등에 제한이 많습니다.
    • 실무에서는 조회 성능, 쿼리 커스터마이징이 매우 중요하기 때문에 명시적인 조인 테이블이 훨씬 유리합니다.

 


3.  실무에서의 주의사항

  • 양방향 관계에서는 연관관계 주인을 명확히 설정해야 함
  • 양쪽에 set 해주는 습관 필요
member.setTeam(team);
team.getMembers().add(member);
  • 지연 로딩(LAZY)을 기본으로 설정하는 것이 성능상 유리
  • @ToString에 연관 필드 포함하면 순환 참조 발생 주의
  • CascadeType, orphanRemoval은 라이프사이클 제어 시 유용하지만 주의해서 사용해야 함

 


4.  정리

관계사용
@ManyToOne 대부분의 관계 (N:1) FK 가짐, 주인
@OneToMany 팀 → 멤버 (1:N) mappedBy 권장
@OneToOne 유저 → 여권 양방향 가능
@ManyToMany 학생 ↔ 강의 실무에서는 중간 엔티티로 대체

 


 5.  마무리

JPA의 관계 매핑은 객체 지향적 설계를 데이터베이스와 자연스럽게 연결해주는 핵심 도구입니다. 하지만 잘못 설정하면 쿼리 폭발, 무한 루프, 비효율적인 SQL이 발생할 수 있어 반드시 원리와 실전 팁을 숙지해야 합니다. 관계 매핑을 정확히 이해하고 적용하는 것이 JPA 마스터로 가는 길의 첫걸음입니다.

함께 보면 좋은 자료

블로그 글 :

 

[JPA] 객체와 데이터베이스를 잇는 다리

JPA(Java Persistence API) JPA(Java Persistence API)는 자바 애플리케이션에서 데이터베이스와의 상호작용을 단순하고 직관적으로 만들어주는 ORM 기술입니다. 복잡한 SQL을 직접 작성하지 않아도 객체 중심

dachaes-devlogs.tistory.com

 


 

728x90
반응형