Dirty Checking

랏 뜨·2024λ…„ 12μ›” 11일

πŸ”Ž Overview

 금일 ν•™μŠ΅ 쀑 κΆκΈˆν•œ 점이 생겼닀.

Β update μž‘μ—…μ„ μ‹€ν–‰ν•˜λ‹ˆ, 쿼리가 μ•„λž˜μ™€ 같이 좜λ ₯λ˜μ—ˆλ‹€.

Hibernate: 
    select
        q1_0.id,
        q1_0.content,
        q1_0.create_date,
        q1_0.subject 
    from
        question q1_0 
    where
        q1_0.id=?

Hibernate: 
    insert 
    into
        answer
        (content, createdate, question_id, id) 
    values
        (?, ?, ?, default)

Hibernate: 
    select
        q1_0.id,
        q1_0.content,
        q1_0.create_date,
        q1_0.subject 
    from
        question q1_0 
    where
        q1_0.id=?

Β  μ™œ selectκ°€ update μ „ν›„λ‘œ 2λ²ˆμ΄λ‚˜ 싀행이 λ˜μ—ˆμ„κΉŒ? κ·Έ 원인은 JPA의 Dirty Checkingμ΄μ—ˆλ‹€.


πŸ“• Dirty Checking

  • JPA κ°€ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ— κ΄€λ¦¬λ˜λŠ” μ—”ν‹°ν‹°μ˜ μƒνƒœλ₯Ό κ²€μ‚¬ν•˜κ³ , κ·Έ 쀑 λ³€κ²½λœ λΆ€λΆ„λ§Œ DB 에 λ°˜μ˜ν•˜λŠ” κΈ°λŠ₯
  • 보닀 효율적인 DB μ—…λ°μ΄νŠΈ κ°€λŠ₯
  • 좔가적인 SQL 쿼리 μž‘μ„±μ΄ ν•„μš” 없이, νŠΈλžœμž­μ…˜ λ‚΄μ—μ„œ μ—”ν‹°ν‹° μƒνƒœλ§Œ λ³€κ²½ν•΄μ£Όλ©΄ μžλ™μœΌλ‘œ 반영
  • DB 와 μ• ν”Œλ¦¬μΌ€μ΄μ…˜ κ°„μ˜ μž‘μ—…μ„ μ΅œμ ν™”

πŸ“ˆ Dirty Checking의 μž‘λ™ 원리

  • μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ™€ _μ—”ν‹°ν‹°μ˜ 초기 μƒνƒœ(μŠ€λƒ…μƒ·)을 λΉ„κ΅ν•˜μ—¬ λ™μž‘

1) μ˜μ†μ„± μ»¨ν…μŠ€νŠΈ

  • JPA λŠ” μ—”ν‹°ν‹° 의 μ €μž₯ 및 쑰회 μ‹œ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈ 에 μ—”ν‹°ν‹° μ €μž₯
  • μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλŠ” 이 μ—”ν‹°ν‹°μ˜ 초기 μƒνƒœ(μŠ€λƒ…μƒ·)λ₯Ό μœ μ§€
  • 이후 μ—”ν‹°ν‹°μ˜ 변경사항 κ°μ‹œ

2) μŠ€λƒ…μƒ·

  • μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ— μ €μž₯된 μ—”ν‹°ν‹°μ˜ 초기 μƒνƒœ
  • JPA λŠ” νŠΈλžœμž­μ…˜μ˜ 컀밋 μ‹œμ μ— μ—”ν‹°ν‹°μ˜ ν˜„μž¬ μƒνƒœμ™€ μŠ€λƒ…μƒ·μ˜ 비ꡐλ₯Ό 톡해 λ³€κ²½ 사항 감지

3) λ³€κ²½ 감지

  • μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ˜ 엔티티와 μŠ€λƒ…μƒ·μ„ λΉ„κ΅ν•˜μ—¬ λ³€κ²½λœ ν•„λ“œ 확인
  • λ³€κ²½λœ ν•„λ“œκ°€ μ‘΄μž¬ν•˜λ©΄ UPDATE 쿼리 μ‹€ν–‰
  • λ³€κ²½λœ ν•„λ“œκ°€ μ—†μœΌλ©΄ 쿼리λ₯Ό μ‹€ν–‰ν•˜μ§€ μ•ŠμŒ
  • DB에 λ³€κ²½ 사항 μ €μž₯(반영)

πŸ“‘ Dirty Chekcing의 νŠΉμ§•

1) νŠΈλžœμž­μ…˜κ³Όμ˜ μ—°κ΄€μ„±

  • Dirty Checking 은 λ°˜λ“œμ‹œ νŠΈλžœμž­μ…˜μ˜ 컀밋 μ‹œμ μ— λ™μž‘
  • 즉, νŠΈλžœμž­μ…˜ λ‚΄μ—μ„œλ§Œ λ™μž‘ν•˜λ―€λ‘œ @Transactional μ–΄λ…Έν…Œμ΄μ…˜μ΄ λ°˜λ“œμ‹œ ν•„μš”

2) μ˜μ† μƒνƒœμ—μ„œλ§Œ λ™μž‘

  • μ€€μ˜μ† μƒνƒœμ—μ„œλŠ” λ™μž‘ν•˜μ§€ μ•ŠμŒ

‼️ Dirty Checking의 μž₯점

  • 개발 생산성 ν–₯상
    • ν™•μ‹€νžˆ λ³€κ²½λœ μ—”ν‹°ν‹°λ§Œ μžλ™μœΌλ‘œ λ°˜μ˜ν•˜λ―€λ‘œ, μˆ˜λ™ 쿼리 μž‘μ„±μ˜ λ²ˆκ±°λ‘œμ›€ κ°μ†Œ
  • μžλ™ν™”λœ 데이터 동기화
    • μ—”ν‹°ν‹° 객체와 λ°μ΄ν„°λ² μ΄μŠ€ κ°„μ˜ μƒνƒœ 동기화λ₯Ό μžλ™μœΌλ‘œ 처리

πŸ€” μ§€μ—° λ‘œλ”©μ—μ„œ Dirty Checking이 μ˜ˆμƒμΉ˜ λͺ»ν•œ SELECT 쿼리λ₯Ό λ°œμƒμ‹œν‚€λŠ” 이유

  • Dirty Checking의 경우, 컀밋 μ‹œμ μ— μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ˜ 엔티티와 μŠ€λƒ…μƒ·μ„ 비ꡐ

  • 이 비ꡐ κ³Όμ •μ—μ„œ Lazy Loading ν•„λ“œ λ˜ν•œ λ§ˆμ°¬κ°€μ§€λ‘œ 체킹 μ‹œλ„

  • Lazy Loading된 μ—°κ΄€ ν•„λ“œλŠ” μ‹€μ œ 데이터λ₯Ό κ°€μ Έμ˜€μ§€ μ•Šκ³ μ„œλŠ” 비ꡐ가 λΆˆκ°€λŠ₯

  • μŠ€λƒ…μƒ·κ³Όμ˜ 비ꡐλ₯Ό μœ„ν•΄μ„œ DB μ—μ„œ 데이터λ₯Ό κ°€μ Έμ˜€λŠ” 좔가적인 SELECT 쿼리 λ°œμƒ

‼️ Dirty Checking은 μ—°κ΄€λœ Lazy Loading ν•„λ“œμ˜ λ³€κ²½ 여뢀도 ν™•μΈν•˜λ©°, λ‘œλ”©λ˜μ§€ μ•Šμ•˜μ„ 경우 JPA λŠ” DB μ—μ„œ ν•΄λ‹Ή 데이터 λ‘œλ“œλ₯Ό 톡해 확인 μ‹œλ„


πŸ™… μ΄λŸ¬ν•œ μƒν˜Έμž‘μš©μ˜ 문제점

1) λΆˆν•„μš”ν•œ 쿼리둜 μΈν•œ μ„±λŠ₯ μ €ν•˜

  • ν•„μš”μ—†λŠ” SELECT 쿼리λ₯Ό μ‹€ν–‰ν•˜λ―€λ‘œ μ„±λŠ₯ μ €ν•˜

2) μ˜λ„μΉ˜ μ•Šμ€ λ™μž‘ λ°œμƒ

  • Lazy Loading 데이터가 μ˜ˆμƒμΉ˜ μ•Šκ²Œ λ‘œλ”©λ  경우, 데이터가 개발자의 μ˜λ„μ™€λŠ” λ‹€λ₯΄κ²Œ 처리될 κ°€λŠ₯μ„± λ°œμƒ
    • νŠΉμ • νŠΈλžœμž­μ…˜μ—μ„œ DB λ‘œλ“œ 없이 μ²˜λ¦¬ν•˜λ € ν–ˆμ§€λ§Œ, Lazy Loading으둜 인해 λΆˆν•„μš”ν•œ 데이터λ₯Ό κ°€μ Έμ˜΄
    • 이둜 μΈν•œ side effect λ°œμƒ κ°€λŠ₯

3) N+1 문제

  • λΆ€λͺ¨ μ—”ν‹°ν‹° 1개λ₯Ό μ‘°νšŒν•  λ•Œ, μžμ‹ μ—”ν‹°ν‹° N λ²ˆμ„ κ°œλ³„μ μœΌλ‘œ μ‘°νšŒν•˜λŠ” μΆ”κ°€ 쿼리 λ°œμƒ
  • μ˜ˆμ‹œ
@Entity
public Class User {
	@Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
    private List<Order> orders;
}

@Entity
public Class Order {
	@Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @ManyToOne(fetch = FetchType.LAZY)
    private User user;
}

List<User> users = userRepository.findAll();	// λΆ€λͺ¨ μ—”ν‹°ν‹°λ₯Ό κ°€μ Έμ˜΄
for (User user : users) {
	user.getOrders().size()	// μžμ‹ μ—”ν‹°ν‹°(Order) 쑰회 (N번의 쿼리 μΆ”κ°€ μ‹€ν–‰)
}
  • ν•œ νŠΈλžœμž­μ…˜ λ‚΄μ—μ„œ μ‹€ν–‰λœλ‹€κ³  κ°€μ •
  • User 의 μ—°κ΄€ 관계인 Order κ°€ Lazy Loading으둜 μ„€μ •λ˜μ–΄ 있기 λ•Œλ¬Έμ—, List<Order> λ₯Ό κ°œλ³„μ μœΌλ‘œ κ°€μ Έμ˜€λ©° μ„±λŠ₯ μ €ν•˜ 유발

4) νŠΈλžœμž­μ…˜ λ²”μœ„μ™€ Lazy Loading

  • νŠΈλžœμž­μ…˜ λ²”μœ„λ₯Ό λ²—μ–΄λ‚˜μ„œ Lazy Loading ν•„λ“œμ— μ ‘κ·Όν•˜λ©΄ LazyInitializationException λ°œμƒ
  • μ΄λŠ” νŠΈλžœμž­μ…˜μ΄ λλ‚œ ν›„ Lazy Loading ν•„λ“œλ₯Ό μ ‘κ·Όν•˜λ©΄, JPAκ°€ μ²˜λ¦¬ν•  수 μ—†κΈ° λ•Œλ¬Έ

πŸ’‘ μ΄λŸ¬ν•œ 문제λ₯Ό ν”Όν•˜λŠ” 방법

1) μ—°κ΄€ λ°μ΄ν„°μ˜ λ‘œλ”©μ„ κΈˆμ§€

  • μ—°κ΄€λœ Lazy Loading ν•„λ“œκ°€ λΆˆν•„μš”ν•˜κ²Œ Dirty Checking λ˜λŠ” 것을 사전에 λ°©μ§€

2) Fetch μ „λž΅ μˆ˜μ •

  • μ—°κ΄€ ν•„λ“œμ˜ FetchType 을 LAZY λŒ€μ‹  EAGER 둜 λ³€κ²½
  • 이 방법을 μ‚¬μš©ν•˜λ©΄ ν•„μš”ν•˜μ§€ μ•Šμ€ 데이터λ₯Ό 항상 λ‘œλ”©ν•˜λ―€λ‘œ μ„±λŠ₯ μ €ν•˜ λ°œμƒ
  • κ·ΈλŸ¬λ―€λ‘œ μΆ”μ²œν•˜λŠ” 방법은 μ•„λ‹˜

3) μ—”ν‹°ν‹° 뢄리

  • μ—°κ΄€ μ—”ν‹°ν‹°λŠ” μ‘°νšŒν•˜μ§€ μ•Šλ„λ‘ DTO 등을 μ‚¬μš©ν•΄μ„œ 사전에 뢄리

4) μ˜μ†μ„± μ»¨ν…μŠ€νŠΈ 관리

  • Dirty Checking λ°œμƒ μ „, μ—°κ΄€λœ Lazy Loading 데이터λ₯Ό κ°•μ œλ‘œ λ‘œλ“œ
  • ν˜Ήμ€ ν•„μš” μ—†λŠ” 경우, detach() λ₯Ό μ΄μš©ν•˜μ—¬ 제거
Hibernate.initialize(user.getOrders());	// Lazy Loading κ°•μ œ μ΄ˆκΈ°ν™”, νŠΈλžœμž­μ…˜ λ²”μœ„ λ‚΄μ—μ„œ κ°•μ œλ‘œ λ‘œλ“œ κ°€λŠ₯
entityManager.detach(user)	// μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ—μ„œ 제거

μ°Έκ³ ) OpenAI. (2024).ChatGPT(4o)[Large language model].https://chatgpt.com/

profile
기둝

0개의 λŒ“κΈ€