6. 연관관계 매핑 - 다중성

2025. 6. 16. 15:06Spring/JPA

1. 다대일 연관관계 [@ManyToOne]
1) 다대일 단방향 
2) 다대일 양방향

2. 일대다 연관관계 [@OneToMany]
1) 일대다 단방향
2) 일대다 양방향

3. 일대일 연관관계 [@OneToOne]
1) 일대일 연관관계 
2) 일대일 연관관계 구현

4. 다대다 연관관계 [@ManyToMany]
1) 이론적 구조
2) 다대다 연결관계의 한계 


1. 다대일 연관관계 [@ManyToOne]

1) 다대일 단방향 

@Entity
public class Member {
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    // Member 테이블에 있는 외래키 "TEAM_ID"를 Member.team 필드로 관리
    private Team team;
}

2) 다대일 양방향

@Entity
public class Member {
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
}

@Entity
public class Team {
    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();
}

3) @ManyToOne 주요 속성

속성 설명 기본값
optional false이면 항상 값 있어야 함 true
fetch Fetch 전략 설정 EAGER
cascade 영속성 전이 설정 -
targetEntity 연관 타입 수동 설정 - (대부분 생략)

4) @OneToMany 주요 속성

속성 설명 기본값
mappedBy 연관관계의 주인 필드 지정 -
fetch Fetch 전략 설정 LAZY
cascade, targetEntity 위와 동일 -

5) @JoinColumn 주요 속성

속성 설명 기본값
name 외래 키 이름 필드명 + '_' + 참조 테이블 PK
referencedColumnName 참조하는 컬럼명 참조 테이블의 PK
foreignKey DDL 시 외래 키 제약 조건 명시 없음
unique, nullable, insertable, updatable 등 @Column과 동일한 속성 사용 가능 -

 

2. 일대다 연관관계 [@OneToMany]

1) 일대다 단방향

@Entity
public class Team {
    @OneToMany
    @JoinColumn(name = "TEAM_ID") 
    // Member 테이블에 있는 외래키 "TEAM_ID"를 Team.members 필드로 관리
    private List<Members> members = new ArrayList<>();
}
  • 일(1)이 연관관계의 주인 (비자연적 구조)
    • 테이블 외래 키는 항상 N쪽 테이블에 존재하기 때문에 Team INSERT 이후 Member UPDATE 쿼리가 발생
    • 외래 키를 관리하는 테이블이 달라 비효율적이므로 추천되지 않음. 
  • 비효율적이고 잘 안 씀. 대신 일대다 단방향보다는 다대일 양방향을 사용하자

2) 일대다 양방향

@Entity
public class Member {
    //ManyToOne(mappedBy = "members") → 이런거 없음!! 
    private Team team;
    
    // mappedBy는 @ManyToOne에서는 사용불가, 항상 @OneToMany 쪽에만 사용해야 함!
    // 따라서 이 필드는 연관관계에 포함되지 않음 (읽기용도 아니고, 그냥 무의미)
}

@Entity
public class Team {
    @OneToMany
    @JoinColumn(name = "TEAM_ID") // 외래키를 직접 Member 테이블에서 관리
    private List<Members> members = new ArrayList<>();
}
  • 일대다 양방향은 공식적으로 존재하지 않음
  • 억지로 구현할 경우 읽기 전용 설정
    @JoinColumn(insertable = false, updatable = false)
  • 일대다 양방향도 되도록 다대일 양방향으로 구성하는 것이 좋음
 

3. 일대일 연관관계 [@OneToOne]

1) 일대일 연관관계 

  • 하나의 엔티티가 오직 하나의 다른 엔티티와만 연결
    • 회원 ↔ 회원 상세정보
    • 주문 ↔ 배송정보
    • Member ↔ Locker : 하나의 회원은 하나의 락커만 갖는 경우 
  • 일대일 연관관계는 외래 키를 어느 테이블에 둘까?   
    • 다대일 관계에선 다(Many) 테이블에 외래 키를 두어야 함 
    • 그런데 일대일 연관관계는 둘 중 아무 테이블에나 외래 키를 둘 수 있고, JPA도 개발자가 지정한 대로 따라간다. 
    • 단, 둘 중 한 테이블에만 외래 키가 있어야 일대일 성립 → 양쪽에 외래 키 두면 이상한 중복관계 됨!
    • 보통은 외래키를 조회, 삽입, 수정이 더 자주 되는 쪽에 둔다 

2) 일대일 연관관계 구현

  • @OneToOne: 일대일 연관관계라는 걸 JPA에 알리는 어노테이션
  • @JoinColumn: 이 엔티티가 외래키를 소유하게 된다
    • 해당 필드로 외래키의 값을 수정하고 저장 가능
    • 이 엔티티가 연관관계의 주인이 됨 
    • 실제 DB에 외래 키 컬럼이 생김 
    • 즉, 외래키가 있는 테이블 = 연관관계의 주인 엔티티가 매핑된 테이블
  • mappedBy
    • 양방향 연관관계인 경우사용
    • 외래키를 사용하지 않는 반대쪽 엔티티에서 사용 
    • 이쪽은 DB에 아무 영향 안 주고 읽기 전용 관계임
@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;
    private String name;

    @OneToOne(mappedBy = "member") // ❌ 이 쪽엔 외래키 없음
    private Locker locker;
}

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

    @OneToOne
    @JoinColumn(name = "member_id") // ✅ 외래키 있음 (연관관계의 주인)
    private Member member;
}
//반대로! 
@Entity
public class Member {
    @OneToOne
    @JoinColumn(name = "locker_id") // ✅ 외래 키 소유자
    private Locker locker;
}

@Entity
public class Locker {
    @OneToOne(mappedBy = "locker") // ❌ 읽기 전용
    private Member member;
}

단방향, 양방향

4. 다대다 연관관계 [@ManyToMany]

1) 이론적 구조

  • 객체에서는 컬렉션 사용으로 양방향 구성 가능
  • 하지만 테이블에서는 연결 테이블을 추가하여 일대다, 다대일 관계로 풀어내야 한다. 

2) 다대다 연결관계의 한계 

  • @ManyToMany 쓰면 JPA가 자동으로 중간 테이블 만들어주긴 하지만 
    • 중간 테이블을 별도로 다루기 어렵고 중간 테이블에 추가 컬럼을 못 넣음 
    • 실무에서는 단순 연결 외에도 수량, 주문시간 등 추가 정보 필요한데 중간 테이블에서는 구현 불가능
  • 따라서 실전에서는 연결 테이블용 중간 엔티티 추가해서 다대다 관계를 해소해야한다. 
    @ManyToMany -> @OneToMany + @ManyToOne
  • 예를 들어 회원(Member)과 제품(Product) 테이블은 다대다 관계이므로
    연결 테이블로 주문(Order) 테이블을 만들어서 해결한다. 

@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;

    @OneToMany(mappedBy = "member") //양방향 거울 역할 
    private List<Order> orders = new ArrayList<>();
}

//중간 엔티티 
@Entity
public class Order {
    @Id @GeneratedValue
    private Long id;

    @ManyToOne
    @JoinColumn(name="MEMBER_ID") 
    private Member member; 
    
    @ManyToOne
    @JoinColumn(name="PRODUCT_ID")
    private Product product; 
}

@Entity
public class Product {
    @Id @GeneratedValue
    private Long id;

    @OneToMany(mappedBy = "product") //양방향 거울역할 
    private List<Order> orders = new ArrayList<>();
}