ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • JPA Dirty Checking
    카테고리 없음 2024. 6. 25. 15:13
    JPA Dirty Checking

     

    • 코드에서 데이터베이스에 엔티티를 update하는 쿼리가 존재하지 않는데, entity의 값을 변경하는 것 만으로도 데이터베이스의 데이터가 변경됨
    • 이는 JPA의 Dirty Checking 때문이며, Dirty Checking은 entity의 상태 변경을 검사하는 것을 의미함
    • JPA에서 트랜잭션이 끝나는 시점에 변화가 있는 모든 entity 객체를 데이터베이스에 자동으로 반영함
    • JPA는 commit하는 순간 flush를 호출하고, 이때 entity와 snapshot을 비교함
    • 영속성 컨텍스트에 처음 저장된 순간 entity의 최초 상태를 1차 캐시에 저장함 (snapshot)
    • commit하는 순간 entity와 snapshot을 비교하여 변경된 값이 있는지 확인하고, 변경된 값이 있다면 update 쿼리를 쓰기 지연 SQL에 저장함
    • 영속 상태가 아닐 경우, 값을 변경해도 데이터베이스에 반영되지 않음

     


     

    JPA Dirty Checking 테스트 코드

    PharmacyRepositoryService.java
    package com.example.pharmacy_navigation.pharmacy.service;
    
    import com.example.pharmacy_navigation.pharmacy.entity.Pharmacy;
    import com.example.pharmacy_navigation.pharmacy.repository.PharmacyRepository;
    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import java.util.Objects;
    
    @Slf4j
    @Service
    @RequiredArgsConstructor
    public class PharmacyRepositoryService {
        private final PharmacyRepository pharmacyRepository;
    
        @Transactional
        public void updateAddress(Long id, String address){
            Pharmacy entity = pharmacyRepository.findById(id).orElse(null);
    
            if(Objects.isNull(entity)){
                log.error("[PharmacyRepositoryService updateAddress] not found id: {}", id);
                return;
            }
    
            entity.changePharmacyAddress(address);
        }
    
        // for test (Transactional annotation 없이 실행)
        public void updateAddressWithoutTransaction(Long id, String address){
            Pharmacy entity = pharmacyRepository.findById(id).orElse(null);
    
            if(Objects.isNull(entity)){
                log.error("[PharmacyRepositoryService updateAddress] not found id: {}", id);
                return;
            }
    
            entity.changePharmacyAddress(address);
        }
    }

     

    • updateAddress( ): Transactional annotation을 적용했기 때문에 entity의 상태 변화가 일어남. Dirty Checking이 성공할 것으로 예상
    • updateAddressWithoutTransaction( ): Transactional annotation을 적용했기 때문에 entity의 상태 변화가 일어나지 않음. Dirty Checking이 실패할 것으로 예상

     

    PharmacyRepositoryServiceTest.groovy
    package com.example.pharmacy_navigation.pharmacy.service
    
    import com.example.pharmacy_navigation.AbstractIntegrationContainerBaseTest
    import com.example.pharmacy_navigation.pharmacy.entity.Pharmacy
    import com.example.pharmacy_navigation.pharmacy.repository.PharmacyRepository
    import org.springframework.beans.factory.annotation.Autowired
    import spock.lang.Specification
    
    class PharmacyRepositoryServiceTest extends AbstractIntegrationContainerBaseTest {
    
        @Autowired
        private PharmacyRepositoryService pharmacyRepositoryService
    
        @Autowired
        private PharmacyRepository pharmacyRepository
    
        def setup() {
            pharmacyRepository.deleteAll()
        }
    
        def "PharmacyRepository update - dirty checking success"() {
            given:
            String inputAddress = "서울 특별시 성북구 종암동"
            String modifiedAddress = "서울 광진구 구의동"
            String name = "은해 약국"
    
            def pharmacy = Pharmacy.builder()
                        .pharmacyAddress(inputAddress)
                        .pharmacyName(name)
                        .build()
    
            when:
            def entity = pharmacyRepository.save(pharmacy)
            pharmacyRepositoryService.updateAddress(entity.getId(), modifiedAddress)
            def result = pharmacyRepository.findAll()
    
            then:
            result.get(0).getPharmacyAddress() == modifiedAddress
    
        }
    
        def "PharmacyRepository update - dirty checking fail"() {
            given:
            String inputAddress = "서울 특별시 성북구 종암동"
            String modifiedAddress = "서울 광진구 구의동"
            String name = "은해 약국"
    
            def pharmacy = Pharmacy.builder()
                    .pharmacyAddress(inputAddress)
                    .pharmacyName(name)
                    .build()
    
            when:
            def entity = pharmacyRepository.save(pharmacy)
            pharmacyRepositoryService.updateAddressWithoutTransaction(entity.getId(), modifiedAddress)
            def result = pharmacyRepository.findAll()
    
            then:
            result.get(0).getPharmacyAddress() == inputAddress
    
        }
    }

     

     

    테스트 코드 실행 결과

    PharmacyRepository update - dirty checking success 테스트 실행 결과

     

    • update 구문이 실행된 것을 확인함
    • Dirty Checking 성공
     PharmacyRepository update - dirty checking fail 테스트 실행 결과

    • Dirty Checking 실패
Designed by Tistory.