λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°

Development/Back-End

JPA 더티 체킹(Dirty Checking) κ°œλ…λΆ€ν„° 싀무 μ£Όμ˜μ‚¬ν•­ (feat. μŠ€λƒ…μƒ· 원리)

πŸ’‘ JPA Dirty Checking(더티 체킹) μ™„λ²½ 정리

μ•ˆλ…•ν•˜μ„Έμš”! μ˜€λŠ˜μ€ JPA의 핡심 κΈ°λŠ₯ 쀑 ν•˜λ‚˜μΈ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈ(Persistence Context),

κ·Έμ€‘μ—μ„œλ„ 데이터 μˆ˜μ •μ„ μžλ™ν™”ν•΄ μ£ΌλŠ” 더티 체킹(Dirty Checking)에 λŒ€ν•΄ 깊이 있게 λ‹€λ€„λ³΄κ² μŠ΅λ‹ˆλ‹€.

 

1. JPA Dirty Checking(더티 체킹)μ΄λž€?

'Dirty'λŠ” "μƒνƒœκ°€ 변경됨"을, 'Checking'은 "검사함"을 μ˜λ―Έν•©λ‹ˆλ‹€.

즉, μ—”ν‹°ν‹°μ˜ μƒνƒœ λ³€ν™”λ₯Ό κ°μ§€ν•΄μ„œ μžλ™μœΌλ‘œ λ°μ΄ν„°λ² μ΄μŠ€μ— λ°˜μ˜ν•˜λŠ” λ©”μ»€λ‹ˆμ¦˜μž…λ‹ˆλ‹€.

덕뢄에 κ°œλ°œμžλŠ” λ³„λ„μ˜ UPDATE 쿼리λ₯Ό 직접 μž‘μ„±ν•˜μ§€ μ•Šμ•„λ„ 객체의 κ°’λ§Œ λ°”κΎΈλŠ” κ²ƒμœΌλ‘œ 데이터λ₯Ό μˆ˜μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€.


2. λ™μž‘ 원리: μŠ€λƒ…μƒ·(Snapshot) 비ꡐ

더티 μ²΄ν‚Ήμ˜ 핡심은 μ˜μ†μ„± μ»¨ν…μŠ€νŠΈ 내뢀에 μ €μž₯λ˜λŠ” μŠ€λƒ…μƒ·μ— μžˆμŠ΅λ‹ˆλ‹€.

  1. μ—”ν‹°ν‹° 쑰회 (Snapshot 생성): DBμ—μ„œ 데이터λ₯Ό μ‘°νšŒν•˜λ©΄ JPAλŠ” μ—”ν‹°ν‹°λ₯Ό μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ— λ‹΄μœΌλ©΄μ„œ, 졜초 μƒνƒœλ₯Ό λ³΅μ‚¬ν•œ μŠ€λƒ…μƒ·μ„ λ‚¨κΉλ‹ˆλ‹€.
  2. μ—”ν‹°ν‹° μˆ˜μ •: μ• ν”Œλ¦¬μΌ€μ΄μ…˜ λ‘œμ§μ—μ„œ μ—”ν‹°ν‹°μ˜ 값을 λ³€κ²½ν•©λ‹ˆλ‹€. μ΄λ•Œ λ©”λͺ¨λ¦¬μƒμ˜ 객체 값은 λ°”λ€Œμ§€λ§Œ DBμ—λŠ” 아직 λ°˜μ˜λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
  3. Flush μ‹œμ  (비ꡐ 및 반영): νŠΈλžœμž­μ…˜μ΄ λλ‚˜λŠ” μ‹œμ μ— flush()κ°€ 호좜되면, JPAλŠ” ν˜„μž¬ μ—”ν‹°ν‹°μ˜ κ°’κ³Ό 졜초의 μŠ€λƒ…μƒ·μ„ λŒ€μ‘°ν•©λ‹ˆλ‹€.
  4. UPDATE μ‹€ν–‰: λ³€κ²½ 사항이 ν™•μΈλ˜λ©΄ μ¦‰μ‹œ UPDATE SQL을 μƒμ„±ν•˜μ—¬ DB에 λ°˜μ˜ν•©λ‹ˆλ‹€.

3. @DynamicUpdate와 쿼리 μž¬μ‚¬μš©

기본적으둜 JPAλŠ” λͺ¨λ“  ν•„λ“œλ₯Ό μ—…λ°μ΄νŠΈν•˜λŠ” 쿼리λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.

 

πŸ’‘ μ™œ λͺ¨λ“  ν•„λ“œλ₯Ό μ—…λ°μ΄νŠΈν• κΉŒ?

  • 쿼리 μž¬μ‚¬μš©: ν•„λ“œ 쑰합이 달라도 쿼리가 항상 λ™μΌν•˜λ―€λ‘œ, λ°μ΄ν„°λ² μ΄μŠ€κ°€ 이전에 λ§Œλ“  μ‹€ν–‰ κ³„νšμ„ μž¬μ‚¬μš©(Parsing/Caching)ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • 일관성: 전체 데이터 ꡬ쑰λ₯Ό νŒŒμ•…ν•˜κΈ° λͺ…ν™•ν•©λ‹ˆλ‹€.

πŸ’‘ @DynamicUpdateλŠ” μ–Έμ œ μ“ΈκΉŒ?

λ§Œμ•½ ν•„λ“œκ°€ λ„ˆλ¬΄ λ§Žμ•„(예: 30개 이상) λ³€κ²½λœ ν•„λ“œλ§Œ 쿼리에 λ°˜μ˜ν•˜κ³  μ‹Άλ‹€λ©΄ 클래슀 상단에 @DynamicUpdateλ₯Ό μ‚¬μš©ν•˜λ©΄ λ©λ‹ˆλ‹€.

  • μˆ˜μ •λœ ν•„λ“œλ§Œ UPDATE 쿼리에 ν¬ν•¨λ©λ‹ˆλ‹€.
  • λ°μ΄ν„°λ² μ΄μŠ€ λ‘œκΉ… μš©λŸ‰μ΄ λ―Όκ°ν•˜κ±°λ‚˜ 전솑 λ°μ΄ν„°λŸ‰μ„ μ΅œμ ν™”ν•΄μ•Ό ν•  λ•Œ μœ λ¦¬ν•©λ‹ˆλ‹€.

4. 더티 체킹이 μž‘λ™ν•˜μ§€ μ•ŠλŠ”λ‹€λ©΄? (Check List)

μ½”λ“œλ₯Ό κ³ μ³€λŠ”λ° DBκ°€ κ·ΈλŒ€λ‘œλΌλ©΄ λ‹€μŒ 두 κ°€μ§€λ₯Ό λ¨Όμ € ν™•μΈν•˜λŠ”κ²Œ μ’‹μŠ΅λ‹ˆλ‹€.

  1. @Transactional λΆ€μž¬: 더티 체킹은 νŠΈλžœμž­μ…˜ λ²”μœ„ λ‚΄μ—μ„œλ§Œ μΌμ–΄λ‚©λ‹ˆλ‹€.
  2. readOnly = true μ˜΅μ…˜: @Transactional(readOnly = true) μ„€μ • μ‹œ, μŠ€ν”„λ§μ€ μ„±λŠ₯ μ΅œμ ν™”λ₯Ό μœ„ν•΄ μŠ€λƒ…μƒ·μ„ 찍지 μ•ŠμœΌλ―€λ‘œ λ³€κ²½ 감지가 μΌμ–΄λ‚˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

5. λͺ…μ‹œμ  μ²˜λ¦¬κ°€ ν•„μš”ν•œ μ˜ˆμ™Έ μΌ€μ΄μŠ€

β‘  save()λ₯Ό ν˜ΈμΆœν•΄μ•Ό ν•˜λŠ” 경우

  • μ€€μ˜μ† μƒνƒœ μˆ˜μ •: μ™ΈλΆ€(DTO)μ—μ„œ λ„˜μ–΄μ˜¨ κ°μ²΄λ‚˜ μ„Έμ…˜μ— μ €μž₯λ˜μ—ˆλ˜ μ—”ν‹°ν‹° λ“± μ˜μ†μ„± μ»¨ν…μŠ€νŠΈκ°€ κ΄€λ¦¬ν•˜μ§€ μ•ŠλŠ” 객체λ₯Ό μˆ˜μ •ν•  λ•Œ.
  • μƒˆλ‘œμš΄ μ—”ν‹°ν‹° 등둝: DB에 μ—†λŠ” 객체λ₯Ό 처음 μ €μž₯ν•  λ•ŒλŠ” λ°˜λ“œμ‹œ save()λ₯Ό 톡해 μ˜μ† μƒνƒœλ‘œ λ§Œλ“€μ–΄μ•Ό ν•©λ‹ˆλ‹€.
  • Tip: μ€€μ˜μ† 객체λ₯Ό μˆ˜μ •ν•  λ•ŒλŠ” save()λ₯Ό ν†΅ν•œ merge보닀, ID둜 λ‹€μ‹œ 쑰회(find)ν•˜μ—¬ μ˜μ† μƒνƒœμ—μ„œ μˆ˜μ •ν•˜λŠ” 방식이 데이터 μœ μ‹€ λ°©μ§€ μΈ‘λ©΄μ—μ„œ 훨씬 μ•ˆμ „ν•©λ‹ˆλ‹€.

β‘‘ flush()λ₯Ό ν˜ΈμΆœν•΄μ•Ό ν•˜λŠ” 경우

  • ID 값을 μ¦‰μ‹œ 확인: MySQL의 Auto-increment μ‚¬μš© μ‹œ, 둜직 쀑간에 μƒμ„±λœ IDκ°€ ν•„μš”ν•˜λ‹€λ©΄ κ°•μ œλ‘œ flush()λ₯Ό ν˜ΈμΆœν•΄μ•Ό ν•©λ‹ˆλ‹€.
  • 볡합 쿼리 μˆ˜ν–‰: 동일 νŠΈλžœμž­μ…˜ λ‚΄μ—μ„œ μ—”ν‹°ν‹° μˆ˜μ • ν›„ JPQLμ΄λ‚˜ Native Queryλ₯Ό 날릴 λ•Œ 데이터 정합성을 μœ„ν•΄ μ‚¬μš©ν•©λ‹ˆλ‹€.

 


마무리

κ²°κ΅­ JPA의 더티 체킹은 κ°œλ°œμžκ°€ 'λ°μ΄ν„°μ˜ μ €μž₯'μ΄λΌλŠ” 관점보닀 '객체의 μƒνƒœ'에 집쀑할 수 있게 ν•΄μ£ΌλŠ” 객체 μ§€ν–₯적인 κΈ°λŠ₯μž…λ‹ˆλ‹€.

νŠΈλžœμž­μ…˜μ˜ λ²”μœ„μ™€ μ—”ν‹°ν‹°μ˜ 생λͺ…μ£ΌκΈ°λ₯Ό μ •ν™•νžˆ μ΄ν•΄ν•˜κ³  μ‚¬μš©ν•œλ‹€λ©΄, 훨씬 더 κ°„κ²°ν•˜κ³  μ•ˆμ „ν•œ μ½”λ“œλ₯Ό μž‘μ„±ν•  수 μžˆμŠ΅λ‹ˆλ‹€ :)

 

 

 

 

λ³Έ ν¬μŠ€νŒ…μ€ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ™€ μ—”ν‹°ν‹° λ§€λ‹ˆμ €μ˜ 관리 λ©”μ»€λ‹ˆμ¦˜μ„ 기반으둜 μž‘μ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€.