@Transactional μΌμ§€λ§Œ Dirty Checking 이 μ•ˆλœ 이유

2023. 3. 12. 17:57γ†πŸŒΏ Spring

λͺ©μ°¨

0. Dirty Checking 이 λ™μž‘ν•˜μ§€ μ•Šμ€ λ‚΄ μ½”λ“œ

1. Dirty Checking 은 μ–΄λ–€ μ‘°κ±΄μ—μ„œ μ‹€ν–‰λ˜λŠ”κ°€?

2. @Transactional 은 μ–΄λ–€ μ›λ¦¬λ‘œ Dirty Checking 을 μ§„ν–‰ν•˜λŠ”κ°€?

3. 그럼 μ–΄λ–»κ²Œ μˆ˜μ •ν•΄μ•Όν•˜λ‚˜?

4. κ²°λ‘ 

 

 

Dirty Checking κ³Ό @Transactional 의 원리λ₯Ό 잘 μ•„μ‹œλŠ” 뢄이라면 이 κΈ€μ—μ„œ μ–»μ–΄κ°€μ‹œλŠ”κ²Œ 많이 없을 수 μžˆμŠ΅λ‹ˆλ‹€. 이 글은 λ¬΄μ§€μ„±μœΌλ‘œ @Transactional 을 μ‚¬μš©ν•œ μ§€λ‚œ 날을 λ°˜μ„±ν•˜κ³  더 κ³΅λΆ€ν•˜κΈ° μœ„ν•΄ μž‘μ„±ν–ˆμŠ΅λ‹ˆλ‹€.

 

0. Dirty Checking 이 λ™μž‘ν•˜μ§€ μ•Šμ€ λ‚΄ μ½”λ“œ

λ„ˆλ¬΄ λΆ€λ„λŸ½μ§€λ§Œ μ²˜μŒμ—” μ΄λ ‡κ²Œ μ½”λ“œλ₯Ό μ§œλ†“κ³  μ™œ Dirty Checking 이 λ˜μ§€ μ•ŠλŠ”μ§€ μ˜μ•„ν–ˆλ‹€.

@Transactional μ–΄λ…Έν…Œμ΄μ…˜μ΄ μžˆλŠ” μƒνƒœμ—μ„œ User 의 property λ₯Ό μˆ˜μ •ν–ˆμœΌλ‹ˆ λ‹Ήμ—°νžˆ μˆ˜μ •λ˜μ–΄μ•Ό ν•˜λŠ”κ±° μ•„λ‹Œκ°€?

userλŠ” λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ μ‘°νšŒν•œ μ—”ν‹°ν‹°λ₯Ό μ™ΈλΆ€μ—μ„œ 인자둜 μ£Όμž…ν–ˆλ‹€.

 

@Component
@RequiredArgsConstructor
public class JwtTokenProvider {

    private final UserRepository userRepository;

    @Transactional
    public void saveRefreshToken(User user, String newRefreshToken) {
	...
        user.setRefreshToken(newRefreshToken);
        ...
    }
}

그럼 μ—¬κΈ°μ„œ λ‚΄κ°€ ν•΄λ³Ό 수 μžˆλŠ” μ§ˆλ¬Έμ€ 2가지닀.

 

1. Dirty Checking 은 μ–΄λ–€ μ‘°κ±΄μ—μ„œ μ‹€ν–‰λ˜λŠ”κ°€?

2. @Transactional 은 μ–΄λ–€ μ›λ¦¬λ‘œ Dirty Checking 을 μ§„ν–‰ν•˜λŠ”κ°€?

 

μœ„ 2가지 물음의 닡을 μ°¨λ‘€λŒ€λ‘œ 풀어보겠닀.

 

1. Dirty Checking 은 μ–΄λ–€ μ‘°κ±΄μ—μ„œ μ‹€ν–‰λ˜λŠ”κ°€?

μš°μ„  Dirty Checking 이 무엇인지 μ•Œμ•„λ³΄μž.

Dirty Checking λŠ” μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜ λ‚΄λΆ€ μ—”ν‹°ν‹°μ˜ λ³€ν™”λ₯Ό μΆ”μ ν•˜κΈ° μœ„ν•΄ ORMμ—μ„œ μ‚¬μš©λ˜λŠ” κ°œλ…μœΌλ‘œ, λ³€ν™”λ₯Ό κ°μ§€ν•˜κ³  νŠΈλžœμž­μ…˜μ΄ commit 되면 μˆ˜μ • 사항을 DB에 μžλ™μœΌλ‘œ λ°˜μ˜ν•˜λŠ” 것을 μ˜λ―Έν•œλ‹€.

 

그럼 Dirty Checking 은 μ–Έμ œ μ‹€ν–‰λ κΉŒ?

Dirty Checking 은 νŠΈλžœμž­μ…˜ μ“°κΈ° μ—°μ‚° 쀑 μ˜μ†μ„± μ»¨ν…μŠ€νŠΈ(Persistent Context) κ°€ μ—”ν‹°ν‹°μ˜ 변경을 감지할 λ•Œ μžλ™μœΌλ‘œ μ‹€ν–‰λœλ‹€.

JPAμ—μ„œλŠ” μ—”ν‹°ν‹°λ₯Ό μ‘°νšŒν•˜λ©΄ ν•΄λ‹Ή μ—”ν‹°ν‹°μ˜ 쑰회 μƒνƒœ κ·ΈλŒ€λ‘œ μŠ€λƒ…μƒ·μ„ λ§Œλ“€μ–΄λ‘λŠ”λ°, 이 μŠ€λƒ…μƒ·μ˜ μƒνƒœμ™€ λΉ„κ΅ν•˜μ—¬ 변경사항이 μžˆλ‹€λ©΄ μ˜€μ—Ό(Dirty)이 감지(Checking) 됐닀고 νŒλ‹¨ν•œλ‹€. 즉 졜초 μ‘°νšŒν•œ μƒνƒœμ™€ λ‹€λ₯Ό λ•Œ Dirty Checking 이 κ°μ§€λœλ‹€.

 

μ˜μ†μ„± μ»¨ν…μŠ€νŠΈ λΌλŠ” 단어가 λ“±μž₯ν–ˆλ‹€. μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλž€ JPA λŸ°νƒ€μž„ ν™˜κ²½μœΌλ‘œ μ—”ν‹°ν‹°μ˜ 수λͺ…μ£ΌκΈ° 및 DB μ˜μ†μ„±μ„ κ΄€λ¦¬ν•˜λŠ” 역할을 λ§‘λŠ”λ‹€. μ‰½κ²Œ 말해 μ—”ν‹°ν‹°μ˜ 영ꡬ μ €μž₯ ν™˜κ²½μ΄μž, μ• ν”Œλ¦¬μΌ€μ΄μ…˜κ³Ό DB μ‚¬μ΄μ—μ„œ 객체λ₯Ό λ³΄κ΄€ν•˜λŠ” 논리적 κ°œλ…μ΄λΌ ν•  수 μžˆλ‹€. μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλΌλŠ” μ—”ν‹°ν‹° 영ꡬ μ €μž₯ ν™˜κ²½μ΄, μ—”ν‹°ν‹°μ˜ 변경을 κ°μ§€ν•œλ‹€λ©΄ Dirty Checking 이 이뀄진닀고 λ³Ό 수 μžˆλ‹€.

μ •λ¦¬ν•˜μžλ©΄, μ—”ν‹°ν‹°κ°€ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ˜ 관리λ₯Ό 받을 λ•Œ Dirty Checking 은 μ‹€ν–‰λœλ‹€.

κ·Έλ ‡λ‹€λ©΄ 이 μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλΌλŠ” 것은 μ–΄λŠ 타이밍에 λ“±μž₯ν•˜λŠ” κ²ƒμΌκΉŒ? 이 μ§ˆλ¬Έμ— λŒ€ν•œ 닡은 @Transactional 이 가지고 μžˆλ‹€.

 

2. @Transactional 은 μ–΄λ–€ μ›λ¦¬λ‘œ Dirty Checking 을 μ§„ν–‰ν•˜λŠ”κ°€?

@Transactional 은 νŠΈλžœμž­μ…˜μ˜ μ‹œμž‘κ³Ό μ’…λ£Œ 지점을 κ΅¬λΆ„ν•˜λŠ” νŠΈλžœμž­μ…˜ 경계λ₯Ό μ„ μ–Έν•  λ•Œ μ‚¬μš©λœλ‹€.

@Transactional이 뢙은 λ©”μ„œλ“œκ°€ 호좜되면 JPA λŠ” μƒˆλ‘œμš΄ νŠΈλžœμž­μ…˜μ„ μƒμ„±ν•˜μ—¬ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ™€ μ—°κ²°μ‹œν‚¨λ‹€.

νŠΈλžœμž­μ…˜μ˜ μ’…λ£Œμ§€μ μ€ ν•΄λ‹Ή νŠΈλžœμž­μ…˜μ΄ commit λ˜λŠ” μ‹œμ μœΌλ‘œλ„ λ³Ό 수 μžˆλŠ”λ°, 이 컀밋 μ‹œμ μ— μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλŠ” νŠΈλžœμž­μ…˜ μ“°κΈ° μž‘μ—…μ— ν•΄λ‹Ήλ˜λŠ” Queryλ₯Ό μƒμ„±ν•œλ‹€. 즉 ν•΄λ‹Ή νŠΈλžœμž­μ…˜ λ‚΄λΆ€μ—μ„œ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈκ°€ μ—”ν‹°ν‹°μ˜ λ³€ν™”λ₯Ό κ°μ§€ν•˜λ©΄ μˆ˜μ •μ‚¬ν•­μ— λ§žλŠ” Query 문을 μƒμ„±ν•œλ‹€.

 

μ—¬κΈ°μ„œ μ€‘μš”ν•œ 점은 μ˜μ†μ„± μ»¨ν…μŠ€νŠΈκ°€ λͺ¨λ“  μ—”ν‹°ν‹°μ˜ λ³€ν™”λ₯Ό 감지(Drity Checking)ν•˜μ§€ μ•ŠλŠ”λ‹€λŠ” 점이닀.

μ—₯? 그럼 μ–΄λ–€ μ—”ν‹°ν‹°μ˜ λ³€ν™”λ§Œ κ°μ§€ν•˜λŠ” κ²ƒμΌκΉŒ? λ°”λ‘œ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈκ°€ κ΄€λ¦¬ν•˜λŠ” μ—”ν‹°ν‹°, 즉 μ˜μ† μƒνƒœμ˜ μ—”ν‹°ν‹°λ§Œ Dirty Checking 의 λŒ€μƒμ΄ λœλ‹€. 엔티티에겐 4가지 μƒνƒœ(λΉ„μ˜μ†, μ‚­μ œ, μ˜μ†, μ€€μ˜μ†)κ°€ 있고, μ˜μ† μƒνƒœμ˜ μ—”ν‹°ν‹°λž€ μ‰½κ²Œ 말해 DB에 μ €μž₯λ˜μ–΄ μžˆλŠ” 데이터라 ν•  수 μžˆλ‹€.

 

λ‹€μ‹œ μ½”λ“œλ‘œ λŒμ•„μ™€λ³΄μž.

Dirty Checking 의 λŒ€μƒμ΄ 되고자 ν•˜κΈΈ λ°”λž¬λ˜ μ—”ν‹°ν‹° User λŠ” λΆ„λͺ… DBλ‘œλΆ€ν„° μ‘°νšŒν•œ 객체닀. ν•˜μ§€λ§Œ 이λ₯Ό μ˜μ† μƒνƒœμ˜ 엔티티라고 ν•  수 μ—†λ‹€. μ™œλƒ? ν•΄λ‹Ή μ—”ν‹°ν‹°λŠ” @Transactional μ™ΈλΆ€μ—μ„œ μ£Όμž…λœ, 즉 ν˜„μž¬ λ©”μ„œλ“œμ˜ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ™€ κ΄€λ ¨μ—†λŠ” 엔티티이기 λ•Œλ¬Έμ΄λ‹€.

 

@Component
@RequiredArgsConstructor
public class JwtTokenProvider {

    private final UserRepository userRepository;

    @Transactional // ν•΄λ‹Ή νŠΈλžœμž­μ…˜ 경계와 μ—°κ²°λ˜λŠ” μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ™€ λ¬΄κ΄€ν•œ user μ—”ν‹°ν‹° 
    public void saveRefreshToken(User user, String newRefreshToken) {
	...
        user.setRefreshToken(newRefreshToken);
        ...
    }
}

 

- μ€€μ˜μ†: Detach 된 μ—”ν‹°ν‹°

- λΉ„μ˜μ† : DB에 반영되기 μ „ 처음 μƒμ„±λœ μ—”ν‹°ν‹°

 

μœ„ 두 μƒνƒœμ˜ μ—”ν‹°ν‹°λŠ” Dirty Checking 의 λŒ€μƒμ΄ μ•„λ‹ˆλ‹€. κ·Έλ ‡κΈ° λ•Œλ¬Έμ— μœ„ μ½”λ“œμ—μ„œ λ©”μ„œλ“œμ— @Transactional 을 뢙이고 user 의 값을 변경해도 DB에 λ°˜μ˜λ˜μ§€ μ•ŠλŠ” 것이닀.

 

인자둜 μ£Όμž…λ°›λŠ” user μ—”ν‹°ν‹°λŠ” 4가지 μƒνƒœ 쀑 μ€€μ˜μ† μƒνƒœλ‘œ, DBμ—λŠ” μ €μž₯돼 μžˆμœΌλ‚˜ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ—μ„œλŠ” λΆ„λ¦¬λœ μƒνƒœλ‹€.

μ–΄λ–€ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈ? λ°”λ‘œ saveRefreshToken() 의 νŠΈλžœμž­μ…˜ 경계와 μ—°κ²°λœ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈ!

μ •λ¦¬ν•˜λ©΄ @Transactional 의 Dirty Checking λŒ€μƒμ€ μ˜μ† μƒνƒœμ˜ 엔티티에 ν•œμ •λ˜λ©°, μ—¬κΈ°μ„œ μ–ΈκΈ‰ν•œ μ˜μ† μƒνƒœμ˜ μ—”ν‹°ν‹°λŠ” ν•΄λ‹Ή νŠΈλžœμž­μ…˜κ³Ό μ—°κ²°λœ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈκ°€ κ΄€λ¦¬ν•˜λŠ” μ—”ν‹°ν‹°λ₯Ό μ˜λ―Έν•œλ‹€. 이λ₯Ό μ œμ™Έν•œ μ—”ν‹°ν‹°(user)의 λ³€κ²½μ—λŠ” ν•΄λ‹Ή 사항이 μ—†λ‹€!

 

3. 그럼 μ–΄λ–»κ²Œ μˆ˜μ •ν•΄μ•Όν•˜λ‚˜?

κ°„λ‹¨ν•˜λ‹€. saveRefreshToken() 을 ν˜ΈμΆœν•˜λŠ” μƒμœ„ κ³„μΈ΅μ—μ„œ User λ₯Ό μ‘°νšŒν•˜μ§€ μ•Šκ³ , saveRefreshToken λ©”μ„œλ“œ λ‚΄λΆ€μ—μ„œ user μ—”ν‹°ν‹°λ₯Ό μ‘°νšŒν•˜λ©΄ λœλ‹€. μ™œ? μ˜μ† μƒνƒœμ˜ μ—”ν‹°ν‹°λ§Œ Dirty Checking 이 적용되기 λ•Œλ¬Έμ΄μ§€. μˆ˜μ •λœ μ½”λ“œλŠ” μ•„λž˜μ™€ κ°™λ‹€.

@Component
@RequiredArgsConstructor
public class JwtTokenProvider {

    private final UserRepository userRepository;

    @Transactional
    public void saveRefreshToken(String refreshToken, String something) {
	...
        User user =  userRepository.findUserBySomething(something);
        user.setRefreshToken(refreshToken);
        ...
    }
}

 

 

4. κ²°λ‘ 

돌이켜 보면 λ‚΄κ°€ 잘λͺ»ν•œ 점은 2가지닀.

0. μ½”λ“œλ₯Ό 그지같이

1. @Transactional 원리와 Dirty Checking 쑰건을 νŒŒμ•…ν•˜μ§€ λͺ»ν•œ 점

2. UserRepository μ˜μ‘΄μ„±μ„ μƒκ°ν•˜μ§€ μ•Šκ³  μ½”λ“œλ₯Ό μ§  점

 

μ‹€μ»· Dirty Chekcingκ³Ό @Transactiona 에 κ΄€ν•œ 글을 μΌμ§€λ§Œ λ‘λ²ˆμ§Έ 원인은 'μ˜μ‘΄μ„±'이닀. UserRepository λ₯Ό μ–΄λ””μ„œ μ‚¬μš©ν• μ§€ μ˜μ‘΄μ„±μ„ 잘 κ³ λ €ν•˜λŠ” μ½”λ“œλ₯Ό μ§°λ‹€λ©΄ μƒμœ„ κ³„μΈ΅μ—μ„œ user λ₯Ό 인자둜 λ„˜κΈ°μ§€ μ•Šμ•˜μ„ 것이닀. κ·Έλž¬λ‹€λ©΄ μ˜μ† μƒνƒœμ˜ 엔티티에 @Transactional 이 μ μš©λ˜μ–΄ Dirty Checking 도 잘 λ™μž‘ν–ˆκ² μ§€.

뭐든 기본이 μ€‘μš”ν•¨μ„ λŠλΌλŠ” μ‚¬κ±΄μ΄μ—ˆλ‹€.