[JPA] JPA Dirty Checking
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 프로그래밍
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개 프로젝트로 완성하는 백엔드 웹개발 - 약국 길찾기 서비스)