[Java] JPA - 다양한 연관관계 매핑

daheenamic·2025년 11월 5일
0

JAVA

목록 보기
36/41

JPA 연관관계 매핑

설계 시 반드시 보는 3가지

다중성 · 방향(단/양방향) · 연관관계의 주인
객체는 참조(Reference)로 관계를 탐색하고, 테이블은 외래키(FK)로 관계를 탐색한다.
이 간극을 메우는 것이 연관관계 매핑이며, 설계 시 아래 3가지 축을 먼저 결정한다.

  1. 다중성: N:1, 1:N, 1:1, N:M
  2. 방향: 단방향/양방향 (객체에만 존재하는 개념)
  3. 연관관계의 주인(Owner): FK를 실제로 등록/수정하는 쪽

다중성

다대일 (N:1) - @ManyToOne

✅ 가장 많이 씀

  • 항상 다(N)쪽에 FK가 있다. (테이블 구조상 자연스러움)
  • 실무 기본값: N:1 단방향 혹은 필요 시 N:1 양방향

왜 실무에서 많이 쓸까?

  1. FK가 N쪽에 있으니 변경 및 추적이 단순하고 쿼리 수도 최소화 된다.
  2. 양방향이 필요하면 주인은 N쪽(Member.team)으로 두고, 반대편(Team.members)은 일기용으로 둔다.

단방향 예시

@Entity
class Member {
    @Id @GeneratedValue Long id;
    private String username;

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
}

@Entity
class Team {
    @Id @GeneratedValue Long id;
    private String name;
}

양방향 추가 (탐색 편의)

@Entity
class Team {
    @Id @GeneratedValue Long id;
    private String name;

    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();
}

편의 메서드(권장)

양쪽 일관성을 맞추기 위해 한쪽에서 둘 다 세팅하는 메서드를 둔다.

// Member
public void changeTeam(Team team) {
    this.team = team;
    if (!team.getMembers().contains(this)) {
        team.getMembers().add(this);
    }
}

일대다 (1:N) - @OneToMany

⚠️ 가능은 하나 권장 X

  • 1인쪽이 FK를 관리하려면, 실제 FK가 있는 다(N)테이블을 UPDATE 해야 해서 쿼리가 추가 된다.
  • 운영 시 "Team만 건드렸는데 Member UPDATE가 나가는"식의 혼란이 생긴다.

예시 (쿼리 흐름)

Member m = new Member("member1");
em.persist(m);

Team t = new Team("teamA");
t.getMembers().add(m);  // 1쪽에서 관리
em.persist(t);

// 실제 실행: INSERT(member), INSERT(team), UPDATE(member.team_id=?)

결론: 일대다 단방향 보다 다대일 양방향을 쓰는게 좋음 (쿼리/운영 모두 깔끔!)

일대다 "양방향처럼 보이게"하는 방법 (비권장)

// 읽기 전용으로 만들어 충돌 방지(공식 양방향 아님)
@ManyToOne
@JoinColumn(name="TEAM_ID", insertable = false, updatable = false)
private Team team;

일대일 (1:1) - @OneToOne

  • 주 테이블에 FK / 대상 테이블에 FK 중 선택
  • 둘 다 장단이 있어 미래 변경 가능성과 조회 패턴을 보고 정함

주 테이블에 FK (개발자 선호)

@Entity
class Member {
    @Id @GeneratedValue Long id;
    private String username;

    @OneToOne
    @JoinColumn(name = "LOCKER_ID", unique = true) // UNIQUE 권장
    private Locker locker;
}

@Entity
class Locker {
    @Id @GeneratedValue Long id;
    private String name;

    @OneToOne(mappedBy = "locker") // 읽기 전용
    private Member member;
}

대상 테이블에 FK

  • DB 개발자들이 선호(1:1 -> 1:N 변경 시 테이블 구조 용이)
  • 단, JPA 단방향 매핑은 지원하지 않음 (양방향은 가능)
    Member에서 Locker를 단방향 참조하고 싶은데 FK가 Locker에 있으면 불가

그렇다면 주테이블 대상테이블은 누가 기준이 될까?
관례적으로 FK를 가진 테이블을 '주'로 잡는 편이 많지만, 실제론 "업무상 주로 조회/관리하는 쪽"을 주로 보는 팀도 있다. 핵심은 FK 위치가 주인 결정에 직접적이라는 점이다.

프록시와 지연로딩 이슈를 간단히 짚고 넘어가자면
대상 테이블에 FK가 있는 1:1 일부 케이스는 프록시 한계로 즉시 로딩이 강제될 수 있다. (세부 내용은 심화에서)

다대다 (N:M) - @ManyToMany

❌ 실무 비추천

  • RDB는 연결 테이블 없이는 진짜 N:M을 표현할 수 없다.
  • 실무에선 연결 테이블에 추가 컬럼(주문 수량/시간 등)이 거의 항상 필요하다.
    그러므로 연결 엔티티 승격이 정석이다.

(비추천) 단순 ManyToMany

@Entity
class Member {
    @Id @GeneratedValue Long id;
    @ManyToMany
    @JoinTable(name = "member_product")
    private List<Product> products = new ArrayList<>();
}

(권장) 연결 엔티티 방식

@Entity
class MemberProduct {
    @Id @GeneratedValue Long id;

    @ManyToOne @JoinColumn(name = "member_id")
    private Member member;

    @ManyToOne @JoinColumn(name = "product_id")
    private Product product;

    // 추가 컬럼
    private int orderAmount;
    private LocalDateTime orderedAt;
}

@ManyToMany는 피하고, MemberProduct 같은 중간 엔티티로 풀어
@ManyToOne + @OneToMany 조합을 쓰자!


방향 (단방향/양방향)

  • 테이블: FK 하나로 양쪽 조인 가능 -> 방향 개념 자체가 없음
  • 객체: 참조 필드가 있는 쪽만 탐색 가능 -> 방향 개념이 생김
    • 한쪽만 참조하면 단방향
    • 양쪽이 서로 참조하면 양방향 (실제로는 단방향 2개의 합)

💡 실무에서의 팁!
단방향으로 먼저 모델 완성하고 필요할 때만 반대편 참조를 추가 (양방향)
테이블 스키마에는 영향 없고, 코드 탐색성만 늘어난다.


연관관계의 주인 (Owner)

  • 주인만 FK를 등록/수정할 수 있다.
  • 주인이 아닌 쪽은 mappedBy로 주인을 가리키며 읽기 전용이다.
  • 기준은 비즈니스 중요도가 아니라 외래키 위치이다.
    -> N:1이면 N인쪽이 주인 (Member.team)
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
// "나는 주인이 아니고, Member의 team 필드에 의해 매핑됐다"는 뜻

그 외 궁금했던 점

N:1 단방향에서의 보충 설명

FK가 N쪽에 있으니 @ManyToOne + @JoinColumn이면 끝
변경도 N쪽만 수정하면 되어서 흐름이 단순하다.
조회가 불편하면 나중에 Team에 members를 추가해 양방향을 열면 된다.

트레이드 오프?

A를 얻으면 B를 포기하는 균형/대가를 말한다.
예를들어 1:N(1이 주인)은 모델이 직관적일 수 있으나 쿼리가 늘고 운영 혼란이 증가한다.(대가)

대상 테이블에 FK 단방향(1:1)은 왜 불가한가?

단방향은 참조 보유쪽에 FK가 있어야 JPA가 매핑할 수 있다.
FK가 반대편에 있으면 누구를 가리키는지 알 수 없어 단방향 매핑이 불가하다.
(양방향은 가능!)

주테이블/대상테이블은 어떻게 정하는지?

DB관점은 FK를 가진 쪽을 주로 보는 경향이 있고, 도메인 관점은 더 자주 조회되고 관리하는것을 주로 삼기도 한다. 핵심은 FK위치 = 주인결정의 1순위라는 점이다.

연관관계 매핑의 80%는 N:1(다대일) + 주인 = FK쪽 + 필요시 양방향으로 정리된다.
나머지 (1:N, 1:1, N:M)는 쿼리/운영/변경 가능성을 보고 선택해야 한다.

원칙은 단순하다.
FK가 있는 쪽이 주인, 양방향은 탐색 편의! 기억해두자

0개의 댓글