🌿Spring

Spring Data JPA μ—”ν‹°ν‹° κ°„ 연관관계 λ§€ν•‘ 방법 정리 with μ˜ˆμ‹œ

μ†Œμ˜ πŸ€ 2025. 3. 7. 16:07

λ“€μ–΄κ°€λ©΄μ„œ

개인적으둜 κ³΅λΆ€ν•œ λ‚΄μš©κ³Ό μƒκ°ν•œ λ‚΄μš©μ„ λ‹΄μ•„ μž‘μ„±ν•œ ν¬μŠ€νŒ…μž…λ‹ˆλ‹€.
ν‹€λ¦° λ‚΄μš©μ΄ μžˆλ‹€λ©΄ λŒ“κΈ€λ‘œ κ³΅μœ ν•΄μ£Όμ„Έμš”!


βœ… κ°œλ… 체크

  • λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œλŠ” μ™Έλž˜ ν‚€λ‘œ μ—°κ΄€ 관계λ₯Ό λ‚˜νƒ€λ‚΄κ³ , 객체 μ§€ν–₯ ν”„λ‘œκ·Έλž˜λ°μ—μ„œλŠ” 객체 κ°„μ˜ 참쑰둜 λ‚˜νƒ€λ‚Έλ‹€.
  • JPAλ₯Ό ν™œμš©ν•œλ‹€λŠ” 것은 RDB μ™Έλž˜ ν‚€λ₯Ό 객체 κ°„ μ—°κ΄€κ΄€κ³„λ‘œ λ§€ν•‘ν•œλ‹€λŠ” 것.
  • μ—”ν‹°ν‹°: λ°μ΄ν„°λ² μ΄μŠ€μ˜ ν…Œμ΄λΈ”κ³Ό 1:1둜 λŒ€μ‘λ˜λŠ” κ°œλ…μ΄λ‹€.
    • ν•œ μΈμŠ€ν„΄μŠ€λŠ” DB ν…Œμ΄λΈ”μ˜ ν•œ row에 ν•΄λ‹Ήν•œλ‹€.
    • JPAμ—μ„œλŠ” μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ— μ˜ν•΄ κ΄€λ¦¬λœλ‹€.
    • μ—”ν‹°ν‹° ν΄λž˜μŠ€μ—λŠ” λ°˜λ“œμ‹œ PK μ‹λ³„μžκ°€ μžˆλ‹€. (@Id μ–΄λ…Έν…Œμ΄μ…˜μœΌλ‘œ μ •μ˜ν•œλ‹€.)
    • κΈ°λ³Έ μƒμ„±μžλ₯Ό ν•„μˆ˜λ‘œ κ°€μ Έμ•Ό ν•œλ‹€. (@NoArgsConstructor μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•œλ‹€.)
    • μ—”ν‹°ν‹°μ˜ ν•„λ“œλŠ” λ°μ΄ν„°λ² μ΄μŠ€ ν…Œμ΄λΈ”μ˜ 컬럼과 λŒ€μ‘λœλ‹€.
  • μ–‘λ°©ν–₯: 두 객체가 μ„œλ‘œ 참쑰용 ν•„λ“œλ₯Ό κ°€μ§€κ³  μžˆλ‹€.
    • A β†’ B, B β†’ A λ‘˜ λ‹€ μ°Έμ‘° κ°€λŠ₯
  • 단방ν–₯: 두 객체 사이에 ν•˜λ‚˜μ˜ 객체만 참쑰용 ν•„λ“œλ₯Ό κ°–κ³  μ°Έμ‘°ν•œλ‹€.
    • A β†’ BλŠ” κ°€λŠ₯ν•˜μ§€λ§Œ, B β†’ AλŠ” λΆˆκ°€λŠ₯
  • DB ꡬ쑰상 단방ν–₯이면 λ°˜λ“œμ‹œ JPA Entity도 단방ν–₯으둜 κ΅¬ν˜„ν•˜λŠ”κ°€?
    • No
    • DB ν…Œμ΄λΈ”μ—μ„œλŠ” ν…Œμ΄λΈ” μ‚¬μ΄μ˜ 연관관계λ₯Ό FK둜 맺을 수 있고 λ°©ν–₯ 상관없이 μ‘°νšŒκ°€ κ°€λŠ₯ν•˜λ‹€.
    • JPAμ—μ„œλŠ” μƒλŒ€ μ—”ν‹°ν‹°λ₯Ό μ°Έμ‘°ν•˜κ³  μžˆμ–΄μ•Όλ§Œ μƒλŒ€ μ—”ν‹°ν‹°λ₯Ό μ‘°νšŒν•  수 μžˆλ‹€. "λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μƒ λ°˜λŒ€ λ°©ν–₯의 μ‘°νšŒλ„ ν•„μš”ν•  λ•Œ"μ—°κ΄€λœ 데이터λ₯Ό 더 μ‰½κ²Œ μ‘°νšŒν•  수 μžˆλ„λ‘ ν•˜κΈ° μœ„ν•΄ μ–‘λ°©ν–₯으둜 μ„€μ •ν•  수 μžˆλ‹€.
  • μ—°κ΄€κ΄€κ³„μ˜ 주인: 두 객체의 관계 쀑 μ œμ–΄μ˜ κΆŒν•œ(데이터 쑰회, μ €μž₯, μˆ˜μ •, μ‚­μ œ)을 κ°€μ§€λŠ” 객체
    • FKλ₯Ό κ΄€λ¦¬ν•˜λŠ”(FKλ₯Ό ν•„λ“œλ‘œ κ°€μ§€κ³  μžˆλŠ”) μ—”ν‹°ν‹°λ₯Ό μ—°κ΄€κ΄€κ³„μ˜ 주인으둜 λ³Έλ‹€.

🎯 1:1 관계(One-to-One)

ν•œ 개의 μ—”ν‹°ν‹°κ°€ λ‹€λ₯Έ ν•˜λ‚˜μ˜ 엔티티와 단 ν•˜λ‚˜μ˜ κ΄€κ²Œλ₯Ό κ°€μ§€λŠ” κ²½μš°μ΄λ‹€.
즉, μ—”ν‹°ν‹° A의 μΈμŠ€ν„΄μŠ€ ν•˜λ‚˜λŠ” μ—”ν‹°ν‹° B의 μΈμŠ€ν„΄μŠ€ ν•˜λ‚˜μ™€ λŒ€μ‘λœλ‹€.

단방ν–₯인 κ²½μš°μ™€ μ–‘λ°©ν–₯인 경우λ₯Ό λ‚˜λˆ„μ–΄ 생각해보겠닀.


단방ν–₯

DBμ—μ„œ λͺ¨λ“  μ‚¬μš©μžλŠ” users ν…Œμ΄λΈ”μ—, μ„œλΉ„μŠ€μ— λ“±λ‘λœ λͺ¨λ“  사진은 photos ν…Œμ΄λΈ”μ— μ €μž₯λ˜μ–΄ μžˆλ‹€κ³  ν•˜μž. photos ν…Œμ΄λΈ”μ—λŠ” μ‚¬μš©μžμ˜ ν”„λ‘œν•„ 이미지도 ν¬ν•¨λ˜μ–΄ μžˆλ‹€.

 

μ‚¬μš©μž(User) 1λͺ…은 1개의 ν”„λ‘œν•„ 이미지(Photo)을 κ°€μ§ˆ 수 μžˆλ‹€.
1개의 ν”„λ‘œν•„ μ΄λ―Έμ§€λŠ” μ‚¬μš©μž 1λͺ…μ—κ²Œ μ†ν•œλ‹€.

 

단방ν–₯인 이유

 

일반적인 μ„œλΉ„μŠ€μ—μ„œ ν”„λ‘œν•„ 사진은 μ‚¬μš©μž 정보λ₯Ό 뢈러올 λ•Œλ§Œ κ°€μ Έμ˜¨λ‹€.
ν”„λ‘œν•„ 사진이 λ”°λ‘œ λ…λ¦½μ μœΌλ‘œ ν•„μš”ν•œ 경우, ν”„λ‘œν•„ 사진이 μ‚¬μš©μž μ •λ³΄κΉŒμ§€ 가져와야 ν•˜λŠ” κ²½μš°λŠ” μ—†λ‹€.

 

UserλŠ” μžμ‹ μ΄ κ°€μ§„ Photoλ₯Ό μ•Œμ•„μ•Ό ν•˜μ§€λ§Œ
PhotoλŠ” μžμ‹ μ„ κ°€μ§„ Userλ₯Ό λͺ°λΌλ„ λœλ‹€.


μ΄λŸ¬ν•œ 이유둜 User -> Photo 단방ν–₯으둜 μ„€μ •ν•œλ‹€.

JPAμ—μ„œ 단방ν–₯, μ–‘λ°©ν–₯을 μ •ν•  λ•ŒλŠ” "객체가 μ„œλ‘œλ₯Ό μ•Œμ•„μ•Ό ν•˜λŠ”κ°€?"λ₯Ό 생각해보면 λœλ‹€.

μ–΄λŠ ν…Œμ΄λΈ”μ΄ FKλ₯Ό κ°€μ Έμ•Ό ν• κΉŒ?

일반적으둜 FK의 주인은 1:N인 κ΄€κ³„μ—μ„œλŠ” N인 μͺ½μ— μ†ν•˜μ§€λ§Œ,
1:1인 κ΄€κ³„μ—μ„œλŠ” μ–΄λŠ μͺ½μ΄ FKλ₯Ό 관리할지,
즉 μ–΄λŠ μ—”ν‹°ν‹°κ°€ μ—°κ΄€ κ΄€κ³„μ˜ 주인이 될지 μ •ν•΄μ•Ό ν•œλ‹€.

 

λ‹€μ–‘ν•œ μ΄μœ κ°€ μžˆκ² μ§€λ§Œ
λ‹€μŒκ³Ό 같은 이유둜 μ •ν–ˆλ‹€.

 

1. 의미적 관계: λˆ„κ°€ 이 관계λ₯Ό 관리할 수 μžˆλ‚˜?

κ°€μž₯ λ¨Όμ € λ– μ˜€λ₯΄λŠ” μ΄μœ λ‹€.
User와 Photo의 μ˜λ―ΈλŠ” μ‚¬μš©μžμ™€ μ‚¬μš©μžμ˜ ν”„λ‘œν•„ 이미지이닀.

 

μ‚¬μš©μžκ°€ μ‚¬μš©μžμ˜ ν”„λ‘œν•„ 이미지에 ν¬ν•¨λ˜κΈ° λ³΄λ‹€λŠ”, ν”„λ‘œν•„ 이미지가 μ‚¬μš©μž 정보에 ν¬ν•¨λ˜λŠ” μͺ½μ΄ μ˜³λ‹€.

사진이 "μ†Œμ†λ  μ‚¬μš©μž"λ₯Ό λ“±λ‘ν•˜κ³  κ΄€λ¦¬ν•˜λŠ” 것보닀
μ‚¬μš©μžκ°€ ν”„λ‘œν•„ 사진을 μ„€μ •ν•˜κ±°λ‚˜ λ³€κ²½ν•˜λŠ” κ°œλ…μ΄ μžμ—°μŠ€λŸ½λ‹€.


Userκ°€ Photo 객체λ₯Ό μ†Œμœ ν•˜κ³ , Photo κ°μ²΄λŠ” νŠΉμ • User 객체에 μ’…μ†λœλ‹€.

πŸ’‘ 주체가 λ˜λŠ” μ—”ν‹°ν‹°(μ†Œμœ μž)κ°€ μ—”ν‹°ν‹°λ₯Ό κ΄€λ¦¬ν•˜λŠ” 게 일반적 -> μ†Œμœ μžκ°€ FKλ₯Ό κ°€μ§„λ‹€.

 

이 κ²½μš°μ—λŠ” Userκ°€ Photo의 id 컬럼과 λ§€ν•‘λ˜λŠ” photo_idλ₯Ό κ°€μ§€λŠ” 게 λ§žλ‹€.

 

2. 쑰회 μ„±λŠ₯ κ³ λ €

λŒ€λΆ€λΆ„μ˜ 경우 User 정보λ₯Ό κ°€μ Έμ˜¬ λ•Œ ν”„λ‘œν•„ 사진을 ν•¨κ»˜ μ‘°νšŒν•  것이닀.
이 상황이 ν”„λ‘œν•„ 사진을 μ‘°νšŒν•  λ•Œ μœ μ € 정보λ₯Ό κ°€μ Έμ˜€λŠ” κ°œλ…λ³΄λ‹€ μ΅μˆ™ν•˜λ‹€.

 

μ΄λ•Œ User에 FKκ°€ μžˆλ‹€λ©΄ μ¦‰μ‹œ Photo와 joinν•΄μ„œ User 정보와 νŠΉμ • Photoλ₯Ό μ‘°νšŒν•˜κΈ° νŽΈν•˜λ‹€.
λ§Œμ•½ Photo에 FKλ₯Ό λ‘”λ‹€λ©΄, λ¨Όμ € User 정보λ₯Ό κ°€μ Έμ˜¨ ν›„ user_idλ₯Ό κ°€μ§„ Photoλ₯Ό ν…Œμ΄λΈ” μ „μ²΄μ—μ„œ μ‘°νšŒν•΄μ•Ό ν•œλ‹€.

πŸ“Œ μ£Όμš” ν‚€μ›Œλ“œ

  • @OneToOne: 1:1 관계λ₯Ό λͺ…μ‹œν•˜λŠ” μ–΄λ…Έν…Œμ΄μ…˜μ΄λ‹€.
  • @JoinColumn: μ—°κ΄€ κ΄€κ³„μ˜ 주인이 FKλ₯Ό λͺ…μ‹œμ μœΌλ‘œ μ§€μ •ν•˜λŠ” μ–΄λ…Έν…Œμ΄μ…˜μ΄λ‹€.
    • @JoinColumn(name = food_id) 이런 μ‹μœΌλ‘œ 컬럼λͺ…을 μ›ν•˜λŠ”λŒ€λ‘œ μ§€μ •ν•˜κ³  싢을 λ•Œ μ‚¬μš©ν•œλ‹€.
    • FKλ₯Ό κ°€μ§€λŠ” κ°μ²΄μ—μ„œλ§Œ μ‚¬μš©ν•œλ‹€.

μ½”λ“œ

User Entity

@Entity
@Table(name="users")
@NoargsConstructor    // Entityλ₯Ό λ§Œλ“€κΈ° μœ„ν•΄μ„œλŠ” κΈ°λ³Έ μƒμ„±μžκ°€ ν•„μˆ˜μ μž„
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToOne
    @JoinColumn(name = "photo_id")
    private Photo photo;

    ...
}

Photo Entity

@Entity
@Table(name="photos")
@NoargsConstructor    
public class Photo {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String fileName;
    private String contentType;
    private Long size;
    ...
}

μ–‘λ°©ν–₯

κ·Έλ ‡λ‹€λ©΄ μ—”ν‹°ν‹°κ°€ μ„œλ‘œ 1:1둜 λŒ€μ‘ν•˜κ³ , μ„œλ‘œμ˜ 쑴재λ₯Ό μ•Œμ•„μ•Ό ν•˜λŠ” μ˜ˆμ‹œλŠ” 무엇이 μžˆμ„κΉŒ?

 

μ‹λ‹Ήμ—μ„œ 1λͺ…μ˜ 고객은 1개의 μŒμ‹λ§Œ μ£Όλ¬Έν•  수 μžˆλ‹€κ³  ν•΄λ³΄μž.

고객은 λ‚˜μ€‘μ— κ²°μ œν•˜λ €λ©΄ μžμ‹ μ΄ μ£Όλ¬Έν•œ μŒμ‹μ„ μ•Œμ•„μ•Ό ν•œλ‹€.
μŒμ‹λ„ 웨이터가 μ„œλΉ™ν•˜λ €λ©΄ μžμ‹ μ„ μ£Όλ¬Έν•œ 고객을 μ•Œμ•„μ•Ό ν•œλ‹€.

λ”°λΌμ„œ 고객 - μŒμ‹μ€ μ„œλ‘œ 객체λ₯Ό μ•Œκ³  μžˆμ–΄μ•Ό ν•˜λŠ” κ΄€κ³„μ΄λ―€λ‘œ μ–‘λ°©ν–₯이닀.


μ΄λ•ŒλŠ” μ„œλ‘œμ˜ μ—”ν‹°ν‹°λ₯Ό μ°Έμ‘°ν•˜κ³  μžˆμ–΄μ•Ό ν•œλ‹€.

고객을 Customer, μŒμ‹μ„ Food라고 ν•˜μž.

참고둜 DBμ—μ„œλŠ” 단방ν–₯ κ΄€κ³„λ‘œ 섀계가 κ°€λŠ₯ν•˜μ§€λ§Œ
쑰회(μŒμ‹ μ„œλΉ™) κΈ°λŠ₯을 νŽΈν•˜κ²Œ ν•˜κΈ° μœ„ν•΄ JPAμ—μ„œ μ–‘λ°©ν–₯ κ΄€κ³„λ‘œ μ„€μ •ν•˜λŠ” 것이닀.

 

이 κ΄€κ³„μ˜ 주인은 λˆ„κ΅¬μΌκΉŒ?
의미적으둜 봀을 λ•Œ, 고객이 νŠΉμ • μŒμ‹μ„ μ£Όλ¬Έν•˜κ±°λ‚˜ μ„ νƒν•˜λŠ” "관리 주체"μ΄λ―€λ‘œ Customerκ°€ 주인이닀. Customer ν΄λž˜μŠ€κ°€ FKλ₯Ό κ°€μ Έμ•Ό ν•œλ‹€.

πŸ“Œ μ£Όμš” ν‚€μ›Œλ“œ

  • mappedBy: μ–‘λ°©ν–₯ κ΄€κ³„μ—μ„œ 이 μ™Έλž˜ ν‚€λ₯Ό κ΄€λ¦¬ν•˜λŠ” μ—”ν‹°ν‹°(주인)κ°€ λˆ„κ΅¬μΈμ§€ μ§€μ •ν•  λ•Œ μ‚¬μš©ν•˜λŠ” μ˜΅μ…˜μ΄λ‹€.
    • 주인이 μ•„λ‹Œ μͺ½μ—μ„œ μ„€μ •ν•œλ‹€.
    • μ—°κ΄€ 관계λ₯Ό κ΄€λ¦¬ν•˜λŠ” ν•„λ“œ 이름을 μ§€μ •ν•΄μ•Ό ν•œλ‹€.
    • DB ν…Œμ΄λΈ” λ‚΄ FK 컬럼 이름이 μ•„λ‹ˆλΌ 주인 μ—”ν‹°ν‹° 클래슀의 μ°Έμ‘° ν•„λ“œ 이름이닀!

μ½”λ“œ

Customer Entity

@Entity
@Table(name="customers")
@NoargsConstructor    
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private int seatIndex;
    ...

    // 고객이 μ‹œν‚€λŠ” 1개의 메뉴
    @OneToOne
    @JoinColumn(name="food_id")
    private Food food;
}

 

Food Entity

주인인 Customerμ—μ„œ Foodλ₯Ό μ°Έμ‘°ν•˜λŠ” ν•„λ“œλŠ” food이닀.
λ”°λΌμ„œ Food μ—”ν‹°ν‹°μ—μ„œ mappedBy μ˜΅μ…˜μœΌλ‘œ Customerμ—μ„œ food ν•„λ“œκ°€ μ—°κ΄€κ΄€κ³„μ˜ μ£ΌμΈμž„μ„ λͺ…μ‹œν•œλ‹€.

@Entity
@Table(name="food")
@NoargsConstructor    
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private int price;
    ...

    // 고객이 μ‹œν‚€λŠ” 1개의 메뉴
    @OneToOne(mappedBy = "food")
    private Customer customer;
}

🎯 1:N/N:1 관계(One-to-Many/Many-to-One)

μ—”ν‹°ν‹° A의 1개의 μΈμŠ€ν„΄μŠ€κ°€ μ—”ν‹°ν‹° B의 N개의 μΈμŠ€ν„΄μŠ€μ™€ λŒ€μ‘ν•  λ•Œ A와 B의 관계λ₯Ό 1:N이라고 ν•œλ‹€.

1λͺ…μ˜ μ‚¬μš©μžκ°€ μ—¬λŸ¬ 개의 κ²Œμ‹œκΈ€μ„ μž‘μ„±ν•  수 μžˆλ‹€.
μ‚¬μš©μžλ₯Ό User, κ²Œμ‹œκΈ€μ„ Post라고 ν•΄λ³΄μž.

DB μƒμ—μ„œμ˜ κ΅¬μ‘°λŠ” μœ„μ™€ κ°™λ‹€.

Postμ—μ„œ FKλ₯Ό κ°€μ§€λ―€λ‘œ μ—°κ΄€κ΄€κ³„μ˜ 주인은 Post이닀.


단방ν–₯

PostλŠ” μžμ‹ μ„ μž‘μ„±ν•œ User 정보λ₯Ό μ•Œμ•„μ•Ό ν•˜μ§€λ§Œ
UserλŠ” μžμ‹ μ΄ μž‘μ„±ν•œ Post 정보가 ν•„μˆ˜μ μΈ 건 아닐 λ•ŒλŠ” 단방ν–₯으둜 κ°€λŠ₯ν•˜λ‹€.

πŸ“Œ μ£Όμš” ν‚€μ›Œλ“œ

  • @ManyToOne: N:1 관계λ₯Ό λ‚˜νƒ€λ‚Έλ‹€.
    • μ—¬λŸ¬ 개의 Postκ°€ ν•˜λ‚˜μ˜ Userλ₯Ό κ°€λ¦¬ν‚€λŠ” 상황이닀.
    • FKλŠ” Nμͺ½μ—μ„œ 관리, 즉 Postκ°€ 주인이닀.
    • Many에 ν•΄λ‹Ήν•˜λŠ” Postμ—μ„œ μ‚¬μš©ν•œλ‹€.

μ½”λ“œ

User Entity

@Entity
@Table(name="users")
@NoargsConstructor    
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String email;
    ...
}

Post Entity

@Entity
@Table(name="posts")
@NoargsConstructor    
public class Post {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;
    private String content;
    ...
    @ManyToOne
    @JoinColumn(name="author_id")
    private User user;
}

μ–‘λ°©ν–₯

λ§Œμ•½ Postκ°€ μž‘μ„±μž Userλ₯Ό μ•Œκ³  μžˆμ–΄μ•Ό ν•˜κ³ ,
User도 μžμ‹ μ΄ μ“΄ Postλ₯Ό μ•Œμ•„μ•Ό ν•  λ•ŒλŠ” μ–‘λ°©ν–₯ κ΄€κ³„λ‘œ μ„€κ³„ν•œλ‹€.
일반적인 μ„œλΉ„μŠ€μ—μ„œλŠ” 이 κ²½μš°κ°€ λ§Žμ§€ μ•Šμ„κΉŒ?

πŸ“Œ μ£Όμš” ν‚€μ›Œλ“œ

  • @OneToMany: 1:N 관계λ₯Ό λ‚˜νƒ€λ‚΄λŠ” μ–΄λ…Έν…Œμ΄μ…˜μ΄λ‹€.
    • 1개의 μ—”ν‹°ν‹°κ°€ μ—¬λŸ¬ 개의 μ—”ν‹°ν‹°λ₯Ό κ°€μ§„λ‹€λŠ” μ˜λ―Έμ΄λ‹€.
    • ν•œ λͺ…μ˜ UserλŠ” μ—¬λŸ¬ 개의 Postλ₯Ό κ°€μ§ˆ 수 μžˆλ‹€.
    • 1μͺ½μ— μ†ν•˜λŠ” μ—”ν‹°ν‹°μ—μ„œ μ‚¬μš©ν•œλ‹€.
    • λ°˜λ“œμ‹œ mappedBy μ˜΅μ…˜μœΌλ‘œ 주인(@ManyToOne을 κ°€μ§„ μ—”ν‹°ν‹°)을 λͺ…μ‹œν•΄μ•Ό ν•œλ‹€.

μ½”λ“œ

User Entity

1:N인 κ΄€κ³„λ‹ˆ N인 객체λ₯Ό List, λ°°μ—΄ 같은 κ°’μœΌλ‘œ μ €μž₯ν•œλ‹€.

@Entity
@Table(name="users")
@NoargsConstructor    
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String email;
    ...

    @OneToMany(mappedBy="author")
    List<Post> posts;
}

Post Entity

@Entity
@Table(name="posts")
@NoargsConstructor    
public class Post {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;
    private String content;
    ...
    @ManyToOne
    @JoinColumn(name="author_id")
    private User author;
}

🎯 N:M 관계(Many-to-Many)

μ—”ν‹°ν‹° A의 N개의 μΈμŠ€ν„΄μŠ€κ°€ μ—”ν‹°ν‹° B의 M개의 μΈμŠ€ν„΄μŠ€μ™€ λŒ€μ‘ν•  λ•Œ A와 B의 관계λ₯Ό N:M이라고 ν•œλ‹€.

κ°€μž₯ 많이 λ“œλŠ” μ˜ˆμ‹œλŠ” λŒ€ν•™κ΅μ—μ„œ "학생과 κ°•μ˜"의 관계이닀.

ν•œ λͺ…μ˜ 학생은 μ—¬λŸ¬ 개의 μˆ˜μ—…μ„ μˆ˜κ°•ν•  수 μžˆλ‹€. β†’ N:1 관계 (Student β†’ Course)
ν•œ 개의 μˆ˜μ—…μ—λŠ” μ—¬λŸ¬ λͺ…μ˜ 학생이 등둝될 수 μžˆλ‹€. β†’ 1:N 관계 (Course β†’ Student)
λ”°λΌμ„œ, N:M 관계λ₯Ό ν˜•μ„±ν•œλ‹€.

 

μ΄λ•ŒλŠ” 두 객체 κ°„μ˜ μ—°κ²° ν…Œμ΄λΈ”μ΄ λ”°λ‘œ ν•„μš”ν•˜λ‹€.
N:M κ΄€κ³„λŠ” 직접 ν‘œν˜„ν•  수 μ—†κΈ° λ•Œλ¬Έμ΄λ‹€.

 

이 ν…Œμ΄λΈ”μ€ μ‹€μ œλ‘œ μœ μ˜λ―Έν•œ 객체λ₯Ό μ €μž₯ν•˜λŠ” ν…Œμ΄λΈ”μ΄ μ•„λ‹ˆλΌ
학생과 κ°•μ˜μ˜ κ΄€κ³„λ§Œμ„ μ €μž₯ν•˜λŠ” 역할을 ν•œλ‹€.

μ΄λ•Œ μ—°κ΄€ κ΄€κ³„μ˜ 주인을 λͺ…ν™•νžˆ λ§ν•˜κΈ°λŠ” 쑰금 μ–΄λ ΅λ‹€.
students, courses ν…Œμ΄λΈ” λͺ¨λ‘ μ„œλ‘œλ₯Ό FK둜 κ°–κ³  μžˆμ§€ μ•ŠκΈ° λ•Œλ¬Έμ΄λ‹€.

 

κ·Έλƒ₯ λ‘˜ 쀑 νŽΈν•œ 것을 주인으둜 ν•œλ‹€.
후에 κΈ°μˆ ν•  @JoinTable을 μ“΄ μͺ½μ„ 주인이라고 λ³Έλ‹€.
μ‹€λ¬΄μ—μ„œλŠ” λ‹€λŒ€λ‹€ 관계λ₯Ό μ§€μ–‘ν•˜λ―€λ‘œ 이 μ •λ„λ§Œ 해도 μΆ©λΆ„ν•œ λ“― ν•˜λ‹€.
μ°Έκ³ : μΈν”„λŸ° QnA

πŸ“Œ μ£Όμš” ν‚€μ›Œλ“œ

  • @ManyToMany: μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜λ©΄ JPAκ°€ μžλ™μœΌλ‘œ μ—°κ²° ν…Œμ΄λΈ”μ„ 생성해쀀닀.
    • μ–‘μͺ½ μ—”ν‹°ν‹° λͺ¨λ‘μ— μ‚¬μš©ν•œλ‹€.
  • @JoinTable: λͺ…μ‹œμ μœΌλ‘œ μ—°κ²° ν…Œμ΄λΈ”μ„ μ„€μ •ν•˜κ³  싢을 λ•Œ μ‚¬μš©ν•œλ‹€.
    • 주인 μ—”ν‹°ν‹°, ν•œ μ—”ν‹°ν‹°μ—μ„œλ§Œ λͺ…μ‹œν•˜λ©΄ λœλ‹€.
    • name: μ—°κ²° ν…Œμ΄λΈ” 이름
    • joinColumns: ν˜„μž¬ μ—”ν‹°ν‹°μ—μ„œ μžˆλŠ” κ°’ 쀑 μ—°κ²° ν…Œμ΄λΈ”μ—μ„œ FK둜 μ“Έ 컬럼λͺ…λ“€
      • Student μ—”ν‹°ν‹°μ—μ„œ μ—°κ²° ν…Œμ΄λΈ”μ„ λ§Œλ“ λ‹€λ©΄ Student의 id ν•„λ“œλ₯Ό μ—°κ²° ν…Œμ΄λΈ”μ˜ FK둜 μ“΄λ‹€.
    • inverseJoinColumns: λ°˜λŒ€ μ—”ν‹°ν‹°μ—μ„œ μžˆλŠ” κ°’ 쀑 μ—°κ²° ν…Œμ΄λΈ”μ—μ„œ FK둜 μ“Έ 컬럼λͺ…λ“€
      • Student μ—”ν‹°ν‹°μ—μ„œ μ—°κ²° ν…Œμ΄λΈ”μ„ λ§Œλ“ λ‹€λ©΄ Course의 id ν•„λ“œλ₯Ό μ—°κ²° ν…Œμ΄λΈ”μ˜ FK둜 μ“΄λ‹€.
  • mappedBy: @JoinTable을 μ“°μ§€ μ•Šμ€ λ‹€λ₯Έ ν•œ μͺ½μ—μ„œλŠ” mappedBy μ˜΅μ…˜μ„ μ‚¬μš©ν•˜μ—¬ μ—°κ΄€ κ΄€κ³„μ˜ 주인/비주인을 κ΅¬λΆ„ν•œλ‹€.
    • Studentκ°€ 주인, Courseκ°€ λΉ„μ£ΌμΈμ΄λ―€λ‘œ Courseμ—μ„œ μ‚¬μš©ν•œλ‹€.

μ½”λ“œ

Student Entity

@Entity
@Table(name="students")
@NoargsConstructor    
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String department;
    private String email;
    ...
    @ManyToMany // N:M 관계 λͺ…μ‹œ
    // μ—°κ²° ν…Œμ΄λΈ” μ§€μ •
    @JoinTable(
        name = "student_course",
        joinColumns = @JoinColumn(name = "student_id"),
        inverseJoinColumns = @JoinColumn(name = "course_id")
    )
    private List<Course> courses;
}

Course Entity

@Entity
@Table(name="courses")
@NoargsConstructor    
public class Course {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String department;
    private String semester;
    private String professor;
    ...
    @ManyToMany(mappedBy="courses") // N:M 관계 λͺ…μ‹œ
    private List<Student> students;
}

참고자료

[JPA] 연관관계 λ§€ν•‘ 주인에 λŒ€ν•΄μ„œ (mappedBy)
TIL-15 JPA Entity 연관관계 1λŒ€1 관계
+기타 μˆ˜μ—… 자료

λ°˜μ‘ν˜•