1 minute read

Updated:

JPA Dirty Checking

  • 코드에서 엔티티 값만 변경했을 뿐인데 데이터베이스 업데이트 쿼리가 발생한다?
  • 이유는 Dirty Checking 덕분이며 Dirty란 상태의 변화가 생긴 정도를 의미한다.
  • 즉, Dirty Checking이란 entity 상태 변경 검사이다.
  • JPA에서 트랜잭션이 끝나는 시점에 변화가 있는 모든 entity 객체를 데이터 베이스에 자동으로 반영해준다.
public class Pharmacy {

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

    private String pharmacyName;
    private String pharmacyAddress;
    private double latitude;
    private double longitude;

    public void changePharmacyAddress(String address) {
        this.pharmacyAddress = address;
    }
}
@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);
    }

Dirty Checking 내부 구조

  • JPA는 commit하는 순간 내부적으로 flush가 호출되고 이때 엔티티와 스냅샷(최초의 값)을 비교한다.
  • 1차 캐시에는 처음 들어온 상태인 엔티티 스냅샷을 넣어두고 commit하는 순간 변경된 값이 있는지 비교하여 변경된 값이 있으면 update 쿼리를 쓰기 지연 SQL에 넣어둔다.

출처 : 자바 ORM 표준 JPA 프로그래밍

출처 : 자바 ORM 표준 JPA 프로그래밍

Dirty Checking 주의사항

  • Dirty Checking은 영속성 컨텍스트가 관리하는 entity에만 적용된다.
  • 영속성 컨텍스트에 처음 저장된 순간 스냅샷을 저장해놓고, 트랜잭션이 끝나는 시점에 비교하여 변경된 부분을 쿼리로 생성하여 데이터베이스로 반영한다.
  • 영속 상태가 아닐 경우 값을 변경해도 데이터베이스에 반영되지 않는다.
  • 트랜잭션 없이 데이터 변경 반영은 일어나지 않는다.
//for test
    public void updateAddressWithoutTransactional(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);
    }
def "PharmacyRepository update - dirty checking fail"() {
        given:
        String inputAddress = "서울 특별시 성북구 종암동"
        String modifyAddress = "서울 광진구 구의동"
        String name = "은혜 약국"

        def pharmacy = Pharmacy.builder()
                .pharmacyAddress(inputAddress)
                .pharmacyName(name)
                .build()

        when:
        def entity = pharmacyRepository.save(pharmacy)
        pharmacyRepositoryService.updateAddressWithoutTransactional(entity.getId(), modifyAddress)

        def result = pharmacyRepository.findAll()

        then:
        result.get(0).getPharmacyAddress() == inputAddress
    }

출처 : 프로그래머스 (10개 프로젝트로 완성하는 백엔드 웹개발 - 약국 길찾기 서비스)

Tags:

Categories:

Updated: