πŸ™†β€β™‚οΈβ€οΈπŸ™…β€β™€οΈ μ§μ‚¬λž‘λΆ€ν„° μ—°μ• κΉŒμ§€ (JPA 연관관계)

KUNΒ·2025λ…„ 5μ›” 22일

β€» λ°œλ‹¨ β€»

JPAλ₯Ό μ“°λ‹€ 보면

@OneToMany, @ManyToOne 같은 μ–΄λ…Έν…Œμ΄μ…˜μ„ 자주 보게 λœλ‹€.
이듀은 κ΄€κ³„ν˜• DB의 μ™Έλž˜ ν‚€(FK) λ₯Ό 객체 μ„Έκ³„μ—μ„œλ„ μ—°κ΄€λœ 객체둜 ν‘œν˜„ν•˜κΈ° μœ„ν•œ 것이닀.
JPAλŠ” 직접 SQL JOIN을 μž‘μ„±ν•˜μ§€ μ•Šκ³ λ„,
μ–΄λ…Έν…Œμ΄μ…˜μœΌλ‘œ μ—°κ΄€ 관계λ₯Ό μ„ μ–Έν•˜κ³ ,
이λ₯Ό λ°”νƒ•μœΌλ‘œ μžλ™ 쑰인 쿼리λ₯Ό μƒμ„±ν•˜κ±°λ‚˜ 객체 탐색이 κ°€λŠ₯ν•˜λ‹€.

그런데 μ†”μ§νžˆ λ§ν•˜λ©΄,
아직 μ € μ–΄λ…Έν…Œμ΄μ…˜λ“€μ„ μ–Έμ œ, μ™œ, μ–΄λ–»κ²Œ 써야 ν•˜λŠ”μ§€ 잘 λͺ¨λ₯΄κ² λ‹€.
κ·Έμ € μ˜ˆμ œμ—μ„œ 보이면 μ“°κ³ , mappedByλŠ” 있으면 λ„£λŠ” μ •λ„μ˜€λ‹€.
단방ν–₯, μ–‘λ°©ν–₯? 기쀀도 μ• λ§€ν•˜κ³  ν—·κ°ˆλ¦΄ 뿐이닀.

κ·Έλž˜μ„œ μ΄λ²ˆμ—”
μ™Έμš°κΈ° μœ„ν•΄μ„œκ°€ μ•„λ‹ˆλΌ, μ΄ν•΄ν•˜κ³  μ„€λͺ…ν•  수 μžˆλ„λ‘
"JPA μ—°κ΄€ 관계 λ§€ν•‘"을 ν™•μ‹€νžˆ 정리해보렀 ν•œλ‹€.


β€» 연관관계 λ°©ν–₯ ( 단방ν–₯, μ–‘λ°©ν–₯ ) β€» - Baeldung

단방ν–₯

μ •μ˜ : ν•œ μͺ½ μ—”ν‹°ν‹°λ§Œ λ‹€λ₯Έ μ—”ν‹°ν‹°λ₯Ό μ°Έμ‘°ν•˜λŠ” 관계.
A -> B λŠ” μ°Έμ‘°ν•˜μ§€λ§Œ, B λŠ” A λ₯Ό λͺ¨λ₯Έλ‹€.

μ‰½κ²Œ λΉ„μœ ν•˜μžλ©΄ λ‹€μŒκ³Ό κ°™λ‹€.
μ§μ‚¬λž‘ ν•œλ‹€κ³  μƒκ°ν•˜μž. πŸ™†β€β™‚οΈβ€οΈ πŸ™…β€β™€οΈ
A(λ‚¨μž)λŠ” B(μ—¬μž) λ₯Ό μ’‹μ•„ν•΄μ„œ κ³„μ†ν•΄μ„œ μ³λ‹€λ³΄μ§€λ§Œ,
B(μ—¬μž)λŠ” A(λ‚¨μž)κ°€ μ’‹μ•„ν•˜λŠ” μ§€ μ‘°μ°¨ λͺ¨λ₯Έλ‹€.

@Entity
public class λ‚¨μž_클레슀 {
    @Id 
    @GeneratedValue
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "μ—¬μž_id")
    private μ—¬μž_클레슀 μ—¬μž;
}

@Entity
public class μ—¬μž_클레슀 {
    @Id 
    @GeneratedValue
    private Long id;
    
    private String name;
}

μ–‘λ°©ν–₯

μ •μ˜ : 두 μ—”ν‹°ν‹°κ°€ μ„œλ‘œλ₯Ό μ°Έμ‘°ν•˜λŠ” 관계

μ‰½κ²Œ λΉ„μœ ν•˜μžλ©΄ λ‹€μŒκ³Ό κ°™λ‹€.
μ„œλ‘œ μ‚¬λž‘ ν•œλ‹€κ³  μƒκ°ν•˜μž. πŸ™†β€β™‚οΈβ€οΈ β€οΈπŸ™†β€β™€οΈ
A(λ‚¨μž)와 B(μ—¬μž)λŠ” μ„œλ‘œ μ’‹μ•„ν•˜λŠ” 사이닀.

@Entity
public class λ‚¨μž_클레슀 {

    @Id 
    @GeneratedValue
    private Long id;

    @ManyToOne
    @JoinColumn(name = "μ—¬μž_id") 
    private μ—¬μž_클레슀 μ—¬μž;
}

@Entity
public class μ—¬μž_클레슀 {

    @Id 
    @GeneratedValue
    private Long id;

    private String name;

    @OneToMany(mappedBy = "μ—¬μž") // μ•„λž˜μ—μ„œ μ„€λͺ…μ˜ˆμ •
    private List<λ‚¨μž_클레슀> λ‚¨μžλ“€ = new ArrayList<>();
}

β€» 연관관계 주인 β€»

μœ— κΈ€μ—μ„œ μš°λ¦¬λŠ” μ—°κ΄€κ΄€κ³„μ˜ λ°©ν–₯ 단방ν–₯κ³Ό μ–‘λ°©ν–₯ 을 λ°°μ› λ‹€.
그럼 이제, μ–‘λ°©ν–₯ κ΄€κ³„μ—μ„œ κΌ­ λ”°λΌλΆ™λŠ” κ°œλ…μΈ
μ—°κ΄€κ΄€κ³„μ˜ 주인은 λ„λŒ€μ²΄ λ¬΄μ—‡μΌκΉŒ?


μ™œ ν•„μš”ν•œκ°€?

μ–‘λ°©ν–₯ μ—°κ΄€κ΄€κ³„μ—μ„œλŠ” μ–‘μͺ½ μ—”ν‹°ν‹°κ°€ μ„œλ‘œλ₯Ό μ°Έμ‘°ν•˜λ―€λ‘œ,
λ‘˜ 쀑 λˆ„κ°€ μ™Έλž˜ ν‚€λ₯Ό 관리할지 λͺ…ν™•νžˆ μ§€μ •ν•˜μ§€ μ•ŠμœΌλ©΄
λΆˆν•„μš”ν•œ 쿼리, 쀑볡 데이터, μƒνƒœ 뢈일치 λ¬Έμ œκ°€ λ°œμƒν•œλ‹€.

κ·Έλž˜μ„œ JPAλŠ” λˆ„κ°€ μ™Έλž˜ ν‚€μ˜ 주인인지 λ°˜λ“œμ‹œ μ§€μ •ν•΄μ•Ό ν•˜κ³ ,
이 역할을 μ—°κ΄€κ΄€κ³„μ˜ 주인 라고 λΆ€λ₯Έλ‹€.

λΉ„μœ λ₯Ό ν•΄λ³΄μž.

κ³ λ°±ν•œ μ‚¬λžŒ : 철수
고백받은 μ‚¬λž‘ : 영희

이 λ‘˜μ€ μΈβ˜…κ·Έλž¨μ— μ„œλ‘œ 처음 μ‚¬κ·€λŠ” 날을 올리자고 μ •ν•˜μ˜€λ‹€.
μ² μˆ˜λŠ” 2025/05/19 일에 사귄닀고 올렸고,
μ˜ν¬λŠ” 2025/05/22 일에 μ‚¬κ·€μ—ˆλ‹€κ³  μ˜¬λ Έλ‹€.

ν•΄κ²°ν•˜κΈ° μœ„ν•΄μ„œλŠ” μ–΄λ–»κ²Œ ν•΄μ•Όν• κΉŒ

μœ„μ—μ„œ μ² μˆ˜μ™€ μ˜ν¬κ°€ μ„œλ‘œ λ‹€λ₯Έ 날에 사귄닀고 λ§ν•˜μ˜€λ‹€.
그럼 이걸 λ³΄λŠ” μ‚¬λžŒλ“€μ€ λˆ„κ΅¬μ˜ 말을 λ―Ώμ–΄μ•Ό ν• κΉŒ?

  • λ¨Όμ € κ³ λ°±ν•œ 철수의 말?
  • 마음의 μ€€λΉ„λ₯Ό λ‹€ ν•˜κ³  λ°›μ•„μ€€ 영희의 말?

사싀 이 λ¬Έμ œλŠ” λˆ„κ°€ λ§žλŠλƒλ³΄λ‹€λ„,
λ‘˜μ΄ 기념일 κ°€μ§€κ³  μ‹Έμš°κ²Œ λ˜λŠ” 게 더 큰 λ¬Έμ œλ‹€. ( λ‘˜ 사이엔 λ―Έλ¬˜ν•œ 균열이 생기겠지 )

이걸 λ°©μ§€ν•˜κΈ° μœ„ν•΄μ„œ JPAλŠ” μ΄λ ‡κ²Œ μ •ν•œλ‹€.
1. μ—°μ•  μƒνƒœλŠ” 주인(철수)κ°€ μ˜¬λ¦°λ‹€.
2. 비주인(영희)λŠ” κ·Έκ±Έ 곡유만 ν•œλ‹€.

철수 = 연관관계 주인 (μ‹€μ œλ‘œ μ™Έλž˜ ν‚€λ₯Ό DB에 μ €μž₯ν•œλ‹€)
@ManyToOne
@JoinColumn(name = "μ—¬μž_id")
private μ—¬μž_클레슀 μ—¬μž;

====================================
영희 = 연관관계 비주인 (κ·Έλƒ₯ 참쑰만 ν•œλ‹€.)
@OneToMany(mappedBy = "μ—¬μž") // <주인> ν•„λ“œλͺ… μ§€μ •
private List<λ‚¨μž_클레슀> λ‚¨μžλ“€;

β€» 연관관계 μ’…λ₯˜ β€»


@ManyToOne : λ§Žμ€ 객체가 ν•˜λ‚˜μ˜ 객체λ₯Ό μ°Έμ‘°ν•˜λŠ” 관계

μ‰½κ²Œ λΉ„μœ ν•΄λ³΄μž.
λ§Žμ€ λ‚¨μžλ“€μ΄ ν•œ λͺ…μ˜ μ—¬μžμ—κ²Œ κ³ λ°±ν•˜λŠ” 상황
πŸ™†β€β™‚οΈπŸ™†β€β™‚οΈπŸ™†β€β™‚οΈπŸ™†β€β™‚οΈβ€οΈ πŸ™…β€β™€οΈ

@Entity
public class λ‚¨μž {

    @Id @GeneratedValue
    private Long id;

    @ManyToOne
    @JoinColumn(name = "μ—¬μž_id") // μ™Έλž˜ ν‚€λŠ” λ‚¨μž μͺ½μ—
    private μ—¬μž 영희;
}

μž₯점

  • μ™Έλž˜ ν‚€λ₯Ό κ°€μ§„ μͺ½μ΄ 주인이 λ˜λ―€λ‘œ 관리가 쉽닀.
    β€œλ‚œ μˆ˜μ§€ μ’‹μ•„ν•΄μš”!” 철수.setμ—¬μž("μˆ˜μ§€") 만 ν•˜λ©΄ DB 에 μ €μž₯되기 λ•Œλ¬Έμ΄λ‹€.
  • DB 섀계 κ΄€μ μ—μ„œλ„ μžμ—°μŠ€λŸ½λ‹€.
    μ—¬μžμͺ½μ— μ—¬λŸ¬ λ‚¨μžλŠ” λ„£λŠ” 것 λ³΄λ‹€λŠ”... λ‚¨μž μͺ½μ— ν•œ μ—¬μžλ§Œ λ„£λŠ” 게 λ‚«μ§€ μ•Šλ‚˜.
  • μ„±λŠ₯ μ΅œμ ν™”κ°€ μˆ˜μ›”ν•˜λ‹€.
    이건 이후에 λ‚˜μ˜¬ fetch μ „λž΅μ΄ ν•„μš”ν•˜λ‹€.

@OneToMany : ν•˜λ‚˜μ˜ 객체가 μ—¬λŸ¬ 객체λ₯Ό μ°Έμ‘°ν•˜λŠ” 관계

μ‰½κ²Œ λΉ„μœ ν•΄λ³΄μž.
ν•œ λͺ…μ˜ μ—¬μžλ₯Ό μ—¬λŸ¬ λ‚¨μžλ“€μ΄ μ’‹μ•„ν•˜λŠ” 건 ν”ν•˜μ§€λ§Œ,
κ·Έ λ°˜λŒ€! μ—¬μžκ°€ 자기λ₯Ό μ’‹μ•„ν•˜λŠ” μ‚¬λžŒλ“€ λͺ©λ‘μ„ κ΄€λ¦¬ν•˜λŠ” 상황이라고 μƒκ°ν•΄λ³΄μž.
πŸ™…β€β™€οΈ ❀️ πŸ™†β€β™‚οΈπŸ™†β€β™‚οΈπŸ™†β€β™‚οΈπŸ™†β€β™‚οΈ

@Entity
public class λ‚¨μž_클레슀 {

    @Id @GeneratedValue
    private Long id;

    @ManyToOne
    @JoinColumn(name = "μ—¬μž_id") // μ™Έλž˜ ν‚€λŠ” μ—¬μ „νžˆ λ‚¨μž μͺ½μ—
    private μ—¬μž_클레슀 μ—¬μž;
}

@Entity
public class μ—¬μž_클레슀 {

    @Id @GeneratedValue
    private Long id;

    private String name;

    @OneToMany(mappedBy = "μ—¬μž") // 'κ³ λ°± 받은 μ‚¬λžŒ' μž…μž₯μ—μ„œ 리슀트 관리
    private List<λ‚¨μž_클레슀> λ‚¨μžλ“€ = new ArrayList<>();
}

β€» Fetch μ „λž΅ (μ¦‰μ‹œ vs μ§€μ—°) β€»

μ •μ˜
@ManyToOne(fetch = FetchType.LAZY)
μ—°κ΄€λœ μ—”ν‹°ν‹°λ₯Ό μ–Έμ œ λΆˆλŸ¬μ˜¬μ§€ μ •ν•˜λŠ” μ „λž΅

μ¦‰μ‹œ(EAGER) : μ—°κ΄€λœ μ—”ν‹°ν‹°λ₯Ό λ°”λ‘œ JOIN ν•΄μ„œ κ°€μ Έμ˜¨λ‹€

"μ†Œκ°œνŒ… λ‚˜μ˜€μžλ§ˆμž μ˜†μžλ¦¬μ— μΉœκ΅¬λ„ 같이 앉아 있음"

β€œμΉœκ΅¬κ°€ ν•„μš”ν•  것 κ°™μ•„μ„œ 미리 λ°λ €μ™”λŠ”λ°,
막상 λŒ€ν™”ν•  λ•Œ 친ꡬ μ–˜κΈ° ν•˜λ‚˜λ„ μ•ˆ λ‚˜μ˜¨λ‹€λ©΄?
– μ•„.. 괜히 데렀왔닀. ( λ‚˜μ€‘μ— μΉœκ΅¬μ—κ²Œ λ°₯ μ‚¬μ€˜μ•Ό 함 )”

μ§€μ—°(LAZY) : ν•„μš”ν•  λ•Œ 쿼리 λ°œμƒ (ν”„λ‘μ‹œ 객체)

"친ꡬ μ–˜κΈ°λŠ” λ‚˜μ€‘μ— ν•„μš”ν•  λ•Œ μΉ΄ν†‘μœΌλ‘œ λ¬Όμ–΄λ³΄λŠ” λŠλ‚Œ"

μΉœκ΅¬κ°€ ν•„μš”ν•˜λ©΄ κ·Έλ•Œ κ°€μ„œ μ „ν™”ν•˜λŠ” 방식


β€» Cascade / OrphanRemoval β€»

μ •μ˜
λΆ€λͺ¨κ°€ μ–΄λ–€ 행동을 ν•˜λ©΄
κ·Έ μžμ‹λ„ 같이 λ”°λΌκ°€κ²Œ ν•˜λŠ” μ„€μ •


Cascade : 같이 μ €μž₯/μ‚­μ œ/κ°±μ‹ κΉŒμ§€ ν•˜κ³  싢을 λ•Œ

"μ—„λ§ˆκ°€ 마트 κ°€λ©΄, 아이도 같이 따라간닀."

μ—„λ§ˆκ°€ μ €μž₯되면 - 아이도 같이 μ €μž₯됨
μ—„λ§ˆκ°€ μ‚­μ œλ˜λ©΄ - 아이도 같이 μ‚­μ œλ¨
μ—„λ§ˆκ°€ μˆ˜μ •λ˜λ©΄ - 아이도 같이 갱신됨

즉, λΆ€λͺ¨κ°€ ν–‰λ™ν•˜λ©΄ JPAκ°€ μžμ‹λ„ μžλ™μœΌλ‘œ μ²˜λ¦¬ν•΄μ€€λ‹€.


OrphanRemoval : λΆ€λͺ¨κ°€ μ—° 끊으면, μžμ‹μ€ 버렀진닀.

"μ—„λ§ˆκ°€ 아이λ₯Ό 버리면... μ•„μ΄λŠ” κ³ μ•„κ°€ 되며, 그리고... "

OrphanRemoval - κ³ μ•„λ©΄ μ‚­μ œν•΄λΌ ( 말이 μ’€ μŽ„μ§€λ§Œ, μ΄λ ‡κ²Œ μƒκ°ν•˜λŠ” 게 μ’‹λ‹€. )

λΆ€λͺ¨μ™€ 연결이 λŠκΈ΄λ‹€λ©΄ μžμ‹μ€ μžλ™μœΌλ‘œ μ‹œμŠ€ν…œμ΄ μ‚­μ œν•œλ‹€.

profile
λ°°μš°λ…ΈλΌ, μ‹€ν—˜ν•˜λ…ΈλΌ, κΈ°λ‘ν•˜λ…ΈλΌ

0개의 λŒ“κΈ€