🌼 Spring 심화 β‘‘ 연관관계, 상속관계 λ§€ν•‘, Proxy, μ˜μ†μ„± 전이 / TIL - day 34

ν•˜λ¦¬λΉ„Β·2025λ…„ 4μ›” 18일
1

🌼 Spring

λͺ©λ‘ 보기
7/11
post-thumbnail

πŸ“– JPA 연관관계

  • 관계 μ’…λ₯˜: @ManyToOne, @OneToMany, @OneToOne, @ManyToMany
  • λ°©ν–₯: 단방ν–₯ / μ–‘λ°©ν–₯ (mappedBy μ‚¬μš© μ‹œ 비주인)
  • μ—°κ΄€κ΄€κ³„μ˜ 주인: μ™Έλž˜ν‚€(FK)λ₯Ό κ°€μ§„ μͺ½ (@JoinColumn 선언됨)

πŸ“Œ 1:N 연관관계 (@OneToMany)

βœ”οΈ 단방ν–₯

  • ν•œ μ—”ν‹°ν‹°κ°€ @OneToManyλ₯Ό 톡해 μ—¬λŸ¬ 엔티티와 관계λ₯Ό λ§ΊλŠ” 경우
  • 주인은 1, λΉ„μ£ΌμΈμ˜ 리슀트λ₯Ό μ»¬λ ‰μ…˜μœΌλ‘œ λ³΄μœ ν•΄μ•Ό 함
  • μ‹€μ œ FKλŠ” 주인 ν…Œμ΄λΈ”μ— μžˆμ–΄μ•Ό ν•˜λ―€λ‘œ, DB 섀계상 λΆ€μžμ—°μŠ€λŸ½κ³  update 쿼리 μΆ”κ°€ λ°œμƒ
  • @JoinColumn으둜 μ™Έλž˜ν‚€ 컬럼 μ§€μ • κ°€λŠ₯
  • μœ μ§€λ³΄μˆ˜, μ„±λŠ₯ 이슈둜 μ‹€λ¬΄μ—μ„œλŠ” 잘 μ‚¬μš©ν•˜μ§€ μ•ŠμŒ

βœ”οΈ μ–‘λ°©ν–₯

  • ν•˜λ‚˜μ˜ μ—”ν‹°ν‹°κ°€ λ‹€λ₯Έ 엔티티와 관계λ₯Ό λ§Ίκ³  κ·Έ λ°˜λŒ€ λ°©ν–₯μ—μ„œλ„ μ„œλ‘œ μ°Έμ‘° κ°€λŠ₯
  • ν•„μš”ν•œ 경우 insertable = false, updatable = false둜 읽기 μ „μš© μ„€μ • κ°€λŠ₯
@ManyToOne
@JoinColumn(name = "company_id", insertable = false, updatable = false)
private Company company;

πŸ“Œ 1:1 연관관계 (@OneToOne)

βœ”οΈ 단방ν–₯ (지원 ❌)

  • μ™Έλž˜ν‚€ 주인 선택 κ°€λŠ₯
  • JPAμ—μ„œ μ§€μ›ν•˜μ§€ μ•ŠλŠ”λ‹€

βœ”οΈ μ–‘λ°©ν–₯

  • μ™Έλž˜ ν‚€κ°€ μžˆλŠ” 곳이 μ—°κ΄€κ΄€κ³„μ˜ 주인 (UNIQUE μ‚¬μš© ❌)

    μž₯점단점
    μ£Ό ν…Œμ΄λΈ”μ£Όν…Œμ΄λΈ”λ§Œ μ‘°νšŒν•΄λ„ λŒ€μƒ ν…Œμ΄λΈ”μ„ μ‘°νšŒν•  수 μžˆλ‹€null ν—ˆμš© (λŒ€μƒ ν…Œμ΄λΈ” 값이 μ—†μ„λ•Œ)
    μ‚­μ œ μ‹œ μ™Έλž˜ ν‚€ κ°’ μ²˜λ¦¬ν•„μš”
    λŒ€μƒ ν…Œμ΄λΈ”μ—°κ΄€κ΄€κ³„ λ³€κ²½μ‹œμ—λ„ ν…Œμ΄λΈ” ꡬ쑰 μœ μ§€μ§€μ—°λ‘œλ”©μ„ 섀정해도 μ¦‰μ‹œ λ‘œλ”©

πŸ“Œ N:M 연관관계 (ManyToMany)

  • @ManyToMany둜 μ„€μ • κ°€λŠ₯ν•˜μ§€λ§Œ μ‹€λ¬΄μ—μ„œλŠ” μ§€μ–‘
    • 쀑간 ν…Œμ΄λΈ”μ΄ 숨겨짐
    • μΆ”κ°€ 컬럼(ex. level, license λ“±) 넣을 수 μ—†μŒ
    • λ³΅μž‘ν•œ 쿼리, μ œμ•½ 쑰건 관리 어렀움

βœ”οΈ 단방ν–₯ / μ–‘λ°©ν–₯

  • @ManyToManyλŠ” 단방ν–₯도 κ°€λŠ₯, ν•„μš”ν•˜λ©΄ μ–‘λ°©ν–₯μœΌλ‘œλ„ μ„€μ • κ°€λŠ₯ (mappedBy μ‚¬μš©)
  • μ‹€λ¬΄μ—μ„œλŠ” 쀑간 ν…Œμ΄λΈ”μ„ 별도 Entity둜 λ§Œλ“€μ–΄ @OneToMany - @ManyToOne으둜 λΆ„λ¦¬ν•˜μ—¬ λ§€ν•‘

🌟 연관관계 λΉ„κ΅ν•˜κΈ°

μ—°κ΄€κ΄€κ³„μΆ”μ²œμ„€λͺ…
1:N 단방ν–₯❌ λΉ„μΆ”μ²œFKκ°€ μžμ‹μ— μžˆλŠ”λ° λΆ€λͺ¨κ°€ κ΄€λ¦¬ν•˜λŠ” ꡬ쑰 β†’ λΉ„νš¨μœ¨μ 
N:1 μ–‘λ°©ν–₯βœ… μΆ”μ²œκ°μ²΄-DB 일관성 μœ μ§€, κ°€μž₯ μžμ—°μŠ€λŸ¬μš΄ ꡬ쑰
1:1βœ… 쑰건뢀 μΆ”μ²œμƒν™©μ— 따라 단방ν–₯ λ˜λŠ” μ–‘λ°©ν–₯ 선택 (FK μœ„μΉ˜ κ³ λ €)
N:M❌ λΉ„μΆ”μ²œμ€‘κ°„ ν…Œμ΄λΈ”μ„ 별도 μ—”ν‹°ν‹°λ‘œ λΆ„λ¦¬ν•˜λŠ” 방식 μΆ”μ²œ

πŸ“– JPA ν…Œμ΄λΈ” 상속 μ „λž΅

κ΄€κ³„ν˜• λ°μ΄ν„°λ² μ΄μŠ€μ˜ ν…Œμ΄λΈ”μ—λŠ” 상속관계가 μ—†μ§€λ§Œ
JPAλ₯Ό ν™œμš©ν•΄ μƒμ†μ²˜λŸΌ μ‚¬μš©ν•  수 μžˆλ‹€!


βœ… 1. SINGLE_TABLE μ „λž΅ (default)

▢️ μ‚¬μš©λ²•

  • @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
  • λΆ€λͺ¨ 클래슀 :@DiscriminatorColumn(name = "dtype") ← μƒλž΅ κ°€λŠ₯
  • μžμ‹ 클래슀 : @DiscriminatorValue("μ›ν•˜λŠ”κ΅¬λΆ„κ°’")

πŸ“Œ νŠΉμ§•

  • ν•˜λ‚˜μ˜ ν…Œμ΄λΈ”μ— λͺ¨λ“  μžμ‹ 클래슀의 정보λ₯Ό μ €μž₯ ( 객체 μ§€ν–₯적인 κ°œλ°œμ— μ–΄μšΈλ¦Ό )
  • @DiscriminatorColumn으둜 ꡬ뢄 ( μ„ μ–Έν•˜μ§€ μ•Šμ•„λ„ DTYPE으둜 컬럼 생성)
  • λ‹¨μˆœν•˜κ³  ν™•μž₯κ°€λŠ₯성이 μ—†λŠ” 경우 μ‚¬μš©

πŸ“Œ μž₯단점

μž₯점단점
쿼리 빠름 (쑰인 μ—†μŒ)단일 ν…Œμ΄λΈ”μ΄λ―€λ‘œ 컬럼이 λ§Žμ•„μ§€κ³ ,
μžμ‹μ΄ λ§€ν•‘ν•œ μ»¬λŸΌμ— NULL λ§Žμ•„μ§
관리 용이DB μ •κ·œν™” X

β–ͺ️ μ‚¬μš© μ˜ˆμ‹œ


βœ”οΈ μ—”ν‹°ν‹° μ½”λ“œ

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) βœ…
@DiscriminatorColumn(name = "dtype") βœ…
public abstract class Item {
    @Id @GeneratedValue
    private Long id;
    private String name;
}

@Entity
@DiscriminatorValue("B") βœ…
public class Book extends Item {
    private String author;
}

@Entity
@DiscriminatorValue("M") βœ…
public class Movie extends Item {
    private String director;
}

βœ”οΈ μ‹€ν–‰ μ½”λ“œ

Item book = new Book();
book.setName("JPA Book");
((Book) book).setAuthor("수린");

em.persist(book); βœ…

βœ”οΈ κ²°κ³Ό ν…Œμ΄λΈ”

item

idnameauthordirectordtype
1JPA Book수린nullB

βœ… 2. JOINED μ „λž΅

▢️ μ‚¬μš©λ²•

  • @Inheritance(strategy = InheritanceType.JOINED)
  • λΆ€λͺ¨ 클래슀 :@DiscriminatorColumn(name = "dtype") ← μƒλž΅ λΆˆκ°€
  • μžμ‹ 클래슀 : @DiscriminatorValue("μ›ν•˜λŠ”κ΅¬λΆ„κ°’")

πŸ“Œ νŠΉμ§•

  • λΆ€λͺ¨, μžμ‹ 각각 ν…Œμ΄λΈ” 생성
  • JOIN을 톡해 쑰회
  • λΉ„μ¦ˆλ‹ˆμŠ€μ μœΌλ‘œ λ³΅μž‘ν•œ 경우 μ‚¬μš© ( 객체 μ§€ν–₯적인 κ°œλ°œμ— μ–΄μšΈλ¦Ό )

πŸ“Œ μž₯단점

μž₯점단점
μ •κ·œν™” 잘됨 (DB 깔끔)μ„±λŠ₯ 느림 (JOIN λ°œμƒ)
μžμ‹ ν…Œμ΄λΈ”λ§ˆλ‹€ ν•„μš”ν•œ 컬럼만 가짐INSERT μ‹œ 쿼리 2번 λ°œμƒ

β–ͺ️ μ‚¬μš©μ˜ˆμ‹œ


βœ”οΈ μ—”ν‹°ν‹° μ½”λ“œ

@Entity
@Inheritance(strategy = InheritanceType.JOINED) βœ…
@DiscriminatorColumn(name = "dtype") βœ…
public abstract class Item {
    @Id @GeneratedValue
    private Long id;
    private String name;
}

@Entity
@DiscriminatorValue("B") βœ…
public class Book extends Item {
    private String author;
}

βœ”οΈ μ‹€ν–‰ μ½”λ“œ

Book book = new Book();
book.setName("JPA Book");
book.setAuthor("수린");

em.persist(book); βœ…

βœ”οΈ κ²°κ³Ό ν…Œμ΄λΈ”

item

idnamedtype
1JPA BookB

book

idauthor
1수린

βœ… 3. TABLE_PER_CLASS μ „λž΅ (μ‚¬μš© ❌)

▢️ μ‚¬μš©λ²•

  • @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)

  • ❌@DiscriminatorColumn, @DiscriminatorValue μ‚¬μš© μ•ˆ 함

πŸ“Œ νŠΉμ§•

  • λΆ€λͺ¨ ν…Œμ΄λΈ” 없이 μžμ‹λ§ˆλ‹€ ν…Œμ΄λΈ” κ°œλ³„ 생성
  • μžμ‹λΌλ¦¬λ„ μ™„μ „ 독립
  • λΆ€λͺ¨ 클래슀 λŒ€μ‹ , 좔상 클래슀 μ‚¬μš©ν•΄μ•Ό 함
  • ❌GenerationType.IDENTIFY μ‚¬μš© λΆˆκ°€

πŸ“Œ μž₯단점 (μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” 것 ꢌμž₯)

μž₯점단점
μžμ‹ ν…Œμ΄λΈ” μ™„μ „ 독립, 빠름쿼리 볡작, UNION ν•„μš”(μžμ‹ ν…Œμ΄λΈ”μ„ μ „λΆ€ 합쳐야함)
not null μ‚¬μš© κ°€λŠ₯λΆ€λͺ¨ 객체 νƒ€μž… μ‘°νšŒμ‹œ μžμ‹ μ „λΆ€ μ‘°νšŒν•΄μ•Ό 함

β–ͺ️ μ‚¬μš© μ˜ˆμ‹œ


βœ”οΈ μ—”ν‹°ν‹° μ½”λ“œ

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) βœ…
public abstract class Item {
    @Id @GeneratedValue
    private Long id;
    private String name;
}

@Entity
public class Book extends Item {
    private String author;
}

βœ”οΈ μ‹€ν–‰ μ½”λ“œ

Book book = new Book();
book.setName("JPA Book");
book.setAuthor("수린");

em.persist(book); βœ…

βœ”οΈ κ²°κ³Ό ν…Œμ΄λΈ”

book

idnameauthor
1JPA Book수린

πŸ’‘ item ν…Œμ΄λΈ”μ€ μ—†μŒ! Book, Movie 각각 독립 ν…Œμ΄λΈ”λ‘œ 생성됨.


🌟 ν…Œμ΄λΈ” μ „λž΅ @Annotation 정리

  • @Inheritance(strategy = InheritanceType.${μ „λž΅})
    1. JOINED : 쑰인
    2. SINGLE_TABLE : 단일 ν…Œμ΄λΈ”(Default)
    3. TABLE_PER_CLASS : κ΅¬ν˜„ ν΄λž˜μŠ€λ§ˆλ‹€ ν…Œμ΄λΈ”
  • @DiscriminatorColumn(name = "dtype")
    • dtype μ»¬λŸΌμ„ 생성함 (κ΄€λ‘€)
    • 이름 λ³€κ²½ κ°€λŠ₯
    • κΈ°λ³Έ κ°’: DTYPE
  • @DiscriminatorValue("${κ°’}")
    • dtype κ°’ μ§€μ •
    • κΈ°λ³Έ κ°’: 클래슀 이름

πŸ“– Proxy

em.getReference()λŠ” ν”„λ‘μ‹œ 객체λ₯Ό λ°˜ν™˜

  • μ§€μ—°λ‘œλ”©μ„ μ§€μ›ν•˜κΈ° μœ„ν•΄ λŒ€λ¦¬ 객체λ₯Ό μ‚¬μš©
  • μ‹€μ œ μ—”ν‹°ν‹° 생성, DB μ‘°νšŒν•˜μ§€ μ•Šκ³ λ„ μ°Έμ‘°ν•  수 있음
  • target : μ‹€μ œ 객체의 참쑰값을 보관
    (== λ‘œλŠ” 비ꡐ λΆˆκ°€, instanceof μ‚¬μš©)

πŸ“Œ em.getReference()

Tutor proxyTutor = em.getReference(Tutor.class, tutor.getId()); βœ… ν”„λ‘μ‹œ 객체 생성 (μ‹€μ œ 객체의 μ°Έμ‘°κ°’λ§Œ κΈ°μ–΅)
System.out.println("proxyTutor.getName() = " + proxyTutor.getName()); βœ… ν•„λ“œ μ ‘κ·Ό ν›„ 쑰회 SQL μ‹€ν–‰ (μ΄ˆκΈ°ν™”)
  • em.getReference()λŠ” ν”„λ‘μ‹œ 객체λ₯Ό 생성
  • ν”„λ‘μ‹œ 객체 생성 ν›„ 졜초 μ‚¬μš©μ‹œ(엔티티에 μ ‘κ·Όμ‹œ)에 ν•œλ²ˆλ§Œ μ΄ˆκΈ°ν™”
  • λ‹€λ§Œ, 이미 μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ— ν•΄λ‹Ή Entityκ°€ 있으면 ν”„λ‘μ‹œ μƒμ„±ν•˜μ§€ μ•Šκ³  κ·Έ 객체 κ·ΈλŒ€λ‘œ λ°˜ν™˜

βœ”οΈ πŸ†š μ˜μ†μ„± μ»¨ν…μŠ€νŠΈ

  • μ˜μ†μ„± μ»¨ν…μŠ€νŠΈ = JPAκ°€ 관리 쀑인 λͺ¨λ“  μ—”ν‹°ν‹°μ˜ μ €μž₯μ†Œ (1μ°¨ μΊμ‹œ)
  • ν”„λ‘μ‹œ = getReference()둜 λ“±λ‘λœ, 아직 μ΄ˆκΈ°ν™”λ˜μ§€ μ•Šμ€ 객체(껍데기)
  • μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλŠ” ν”„λ‘μ‹œλ₯Ό ν¬ν•¨ν•˜λŠ” μƒμœ„ κ°œλ…

βœ”οΈ κ΄€λ ¨ μ˜ˆμ™Έ - LazyInitalizationException

  • ν”„λ‘μ‹œ 객체λ₯Ό 생성 ν›„, detachν–ˆκ±°λ‚˜, ν”„λ‘μ‹œ 객체λ₯Ό μƒμ„±ν•˜μ§€ μ•Šκ³  객체에 μ ‘κ·Όν•˜κ³ μž ν–ˆμ„λ•Œ!
  • ν”„λ‘μ‹œ 객체가 μ—†λ‹€ or μ˜μ†μƒνƒœκ°€ μ•„λ‹ˆλ‹€!

πŸ“Œ em.find()와 비ꡐ

λ©”μ„œλ“œem.find()em.getReference()
DB 쿼리 λ°œμƒ 여뢀쑰건뢀 βœ…βŒ ➝ ν”„λ‘μ‹œ 객체 λ°˜ν™˜
μ„€λͺ…1μ°¨ μΊμ‹œ ➝ DB 쑰회 + μΊμ‹œμ— μΆ”κ°€μ‹€μ œ DB μ‘°νšŒν•˜μ§€ μ•Šκ³  ν”„λ‘μ‹œ 객체만 등둝

🌟 μ§€μ—°λ‘œλ”©, μ¦‰μ‹œλ‘œλ”© 정리

μ§€μ—°λ‘œλ”©μ¦‰μ‹œλ‘œλ”©
fetch = FetchType.LAZYfetch = FetchType.EAGER
proxy 객체λ₯Ό 쑰회proxy 쑰회 ❌, μ—°κ΄€λœ 객체 ν•œλ²ˆμ— 쑰회
μ—°κ΄€λœ 객체λ₯Ό 맀번 μ‘°νšŒν•˜λŠ”κ²Œ 낭비인 κ²½μš°μ—°κ΄€λœ 객체λ₯Ό 맀번 ν•¨κ»˜ μ‘°νšŒν•˜λŠ” 것이 효율적인 경우

πŸ“– μ˜μ†μ„± 전이

πŸ“Œ Cascade

    @OneToMany(mappedBy = "category", cascade = CascadeType.ALL) 
    								  πŸ”Ό μΉ΄ν…Œκ³ λ¦¬-ν”„λ‘œλ•νŠΈ 리슀트 κ°„μ˜ μ˜μ†μ„± 전이 섀정함
    private List<Product> productList = new ArrayList<>();
  • νŠΉμ • μ—”ν‹°ν‹°λ₯Ό κ°€μ§€κ³  μž‘μ—…ν•  λ•Œ, μ—°κ΄€λœ 엔티티에도 λ™μΌν•œ μž‘μ—…μ„ μžλ™μœΌλ‘œ ν•˜κ²Œ μ„€μ •
  • μ§€μ—°λ‘œλ”©, μ¦‰μ‹œλ‘œλ”©κ³ΌλŠ” 무관
  • @OneToManyκ°€ μ„ μ–Έλœ μͺ½ = λΆ€λͺ¨ μͺ½μ— μ„ μ–Έ

πŸ“Œ CasecadeType별 의미

βœ”οΈ CascadeType.ALLβœ”οΈ CascadeType.PERSISTβœ”οΈ CascadeType.REMOVE
PERSIST + REMOVE + MERGE
+ REFRESH + DETACH ... λ“±
em.persist(parent) ν–ˆμ„ λ•Œ
β†’ μžμ‹λ“€λ„ μžλ™μœΌλ‘œ persist()
em.remove(parent) ν–ˆμ„ λ•Œ
β†’ μžμ‹λ“€λ„ μžλ™μœΌλ‘œ remove()
λͺ¨λ“  전이 포함INSERT 쿼리 같이 λ°œμƒDELETE 쿼리 같이 λ°œμƒ


▢️ κ³ μ•„ 객체

  • λΆ€λͺ¨ μ—”ν‹°ν‹°μ™€μ˜ 관계가 끊긴 μžμ‹ μ—”ν‹°ν‹°

βœ”οΈ orphanRemoval

  • λΆ€λͺ¨-μžμ‹ 관계가 끊긴 μžμ‹ 객체λ₯Ό DBμ—μ„œ μžλ™μœΌλ‘œ μ‚­μ œν• μ§€ μ„€μ •

  • @OneToManyκ°€ μ„ μ–Έλœ μͺ½ = λΆ€λͺ¨μ— μ„ μ–Έ

    false (κΈ°λ³Έκ°’)true
    μžμ‹μ€ DB에 κ·ΈλŒ€λ‘œ λ‚¨μŒ (β—μˆ˜λ™ μ‚­μ œ ν•„μš”)μžμ‹μ€ DELETE 쿼리 λ°œμƒ, DBμ—μ„œλ„ 제거됨

β–ͺ️ μ—”ν‹°ν‹° μ½”λ“œ

@Entity
@Table(name = "category")
public class Category {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
	
    @OneToMany(mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true) βœ…
    private List<Product> productList = new ArrayList<>();

    public Category() {
    }

	...
    
    public void addProduct(Product product) {
        productList.add(product);
        product.setCategory(this); // 연관관계 주인 섀정을 λ©”μ„œλ“œλ‘œ!
    }
}

β–ͺ️ μ‹€ν–‰ μ½”λ“œ

public class OrphanRemovalMain {
    public static void main(String[] args) {

		...

        try {
            // 1. λΆ€λͺ¨ 생성
            Category category = new Category("food");

            // 2. μžμ‹ 생성 (아직 연관관계 X)
            Product product1 = new Product("pizza");
            Product product2 = new Product("kimchi");

            // 3. 연관관계 μ„€μ • (addProductκ°€ μ–‘λ°©ν–₯ 연관관계 μ£Όμž…)
            category.addProduct(product1);
            category.addProduct(product2);

            // 4. λΆ€λͺ¨λ§Œ persistν–ˆμ§€λ§Œ, cascade 덕뢄에 μžμ‹λ„ μžλ™ persist됨
            em.persist(category); // cascade = ALL

            // 5. DB에 반영 + μ˜μ†μ„± μ»¨ν…μŠ€νŠΈ 비움
            em.flush(); // INSERT μ‹€ν–‰
            em.clear(); // 1μ°¨ μΊμ‹œ μ΄ˆκΈ°ν™”

            // 6. λ‹€μ‹œ λΆ€λͺ¨ 쑰회
            Category findCategory = em.find(Category.class, category.getId());

            // 7. μžμ‹μ„ μ»¬λ ‰μ…˜μ—μ„œ 제거!
            findCategory.getProductList().remove(0);
            // βœ… orphanRemoval = true μ΄λ―€λ‘œ,
            // μ—¬κΈ°μ„œ "μžμ‹ 1κ°œκ°€ λΆ€λͺ¨μ—μ„œ μ œκ±°λλ„€ β†’ κ³ μ•„ 객체!" 라고 νŒλ‹¨
            // β†’ νŠΈλžœμž­μ…˜ 컀밋 μ‹œ DELETE 쿼리 λ‚ λ¦Ό! πŸ”₯

            transaction.commit(); // βœ… μ—¬κΈ°μ„œ μžμ‹ 1개 DELETE 쿼리 λ°œμƒ

			...
            
    }
}

🌟 μ˜μ†μ„± μ‚­μ œ 전이 πŸ†š κ³ μ•„ 객체 제거

ν•­λͺ©CascadeType.RemoveorphanRemoval=true
λͺ©μ λΆ€λͺ¨ 객체 μ‚­μ œμ‹œ μžμ‹ 객체도 μ‚­μ œλΆ€λͺ¨κ°€ μ—†μ–΄μ§„ μžμ‹ κ°μ²΄λŠ” μžλ™ μ‚­μ œ
μž‘λ™ μ‹œμ λΆ€λͺ¨κ°€ remove 될 λ•ŒλΆ€λͺ¨ μ»¬λ ‰μ…˜μ—μ„œ μžμ‹μ„ μ œκ±°ν•  λ•Œ (관계 끊길 λ•Œ)
μ‚¬μš© 쑰건뢀λͺ¨-μžμ‹ 연관관계 + cascade μ„€μ •λΆ€λͺ¨-μžμ‹ μ—°κ΄€κ΄€κ³„λ§Œ μžˆμ–΄λ„ κ°€λŠ₯ (단독 μ‚¬μš© κ°€λŠ₯ βœ…)

0개의 λŒ“κΈ€