영속성 컨텍스트와 변경 감지 완전 정복
JPA로 개발을 하다 보면 아래와 같은 코드를 자주 보게 됩니다.
@Transactional
public void updateUsername(Long id, String newName) {
User user = userRepository.findById(id).orElseThrow();
user.setUsername(newName); // 이게 저장된다고?
}
|
cs |
별도로 save()를 호출하지 않았는데, 값이 데이터베이스에 반영됩니다.
이러한 동작은 JPA의 변경 감지(Dirty Checking) 기능 덕분입니다.
이번 글에서는 이 원리가 어떻게 작동하는지 설명드리고, 실무에서 자주 발생하는 실수들과 실전 팁도 함께 정리하겠습니다.
✅ 영속성 컨텍스트란 무엇인가요?
JPA에서 엔티티 객체를 관리하는 공간을 영속성 컨텍스트(Persistence Context) 라고 합니다.
이는 EntityManager가 엔티티의 상태를 추적하고 관리하는 일종의 저장소라고 볼 수 있습니다.
JPA에서 엔티티는 총 4가지 상태를 가집니다:
비영속 | 단순히 new로 생성한 객체 (DB와 무관) |
영속 | 영속성 컨텍스트에 저장된 상태 (변경 감지 O) |
준영속 | 컨텍스트에서 분리된 상태 (detach() 등으로) |
삭제 | 삭제로 지정된 상태 (remove()) |
⚙️ 변경 감지(Dirty Checking)는 어떻게 작동할까요?
트랜잭션 내부에서 영속 상태인 엔티티가 수정되면,
트랜잭션이 커밋되는 시점에 JPA는 변경된 필드만 감지하여 UPDATE 쿼리를 자동으로 생성합니다.
🔸 예제
위 코드에서는 별도의 save() 호출이 없어도
트랜잭션 종료 시점에 flush()가 호출되고, 변경 내용이 데이터베이스에 반영됩니다.
❗ 실무에서 자주 발생하는 실수들
1. 트랜잭션 없이 변경했는데 반영된 줄 착각하는 경우
✔️ 해결 방법: 수정 로직에는 반드시 @Transactional이 선언되어 있어야 합니다.
2. DTO 매핑 후 merge() 사용
merge()는 새로운 객체의 상태를 기준으로 기존 객체를 덮어쓰는 방식입니다.
필드가 누락되면 해당 값이 null로 업데이트되는 위험이 있습니다.
✔️ 해결 방법: 가능하다면 기존 엔티티를 조회한 후 필요한 필드만 명시적으로 수정하는 것이 안전합니다.
🧠 실전에서 기억해두면 좋은 팁
- @Transactional은 변경 감지의 핵심입니다.
- DTO → Entity 매핑 시에는 merge() 대신 조회 + setter 방식이 안전합니다.
- flush()는 데이터베이스 반영을 강제로 수행하지만, 트랜잭션 커밋이 아니면 실제 저장은 되지 않습니다.
- detach(), clear() 등을 호출하면 영속 상태가 해제되어 변경 감지가 작동하지 않습니다.
🔚 마무리하며
JPA는 객체 지향적인 데이터 관리를 가능하게 해주는 매우 편리한 도구입니다.
하지만 그 내부 동작 원리를 정확히 이해하지 못하면, 예상치 못한 결과나 버그가 발생할 수 있습니다.
오늘 살펴본 영속성 컨텍스트와 변경 감지 개념은 JPA를 안정적으로 활용하기 위한 기본 중의 기본입니다.
이 원리를 숙지해두면 엔티티 상태 변화에 대한 이해도 높아지고, 실무에서도 더 안정적인 코드를 작성할 수 있습니다.
💬 실무에서 경험했던 JPA 관련 시행착오가 있다면 댓글로 공유해 주세요.
다음 글에서는 detach(), clear(), flush() 같은 고급 메서드와
JPA 테스트 시 주의해야 할 점에 대해서도 다룰 예정입니다. 😊
'스프링' 카테고리의 다른 글
[JPA] 영속성 관리[5] (0) | 2024.12.10 |
---|---|
[JPA] 영속성 관리[4] (1) | 2024.12.09 |
[JPA] 영속성 관리[3] (1) | 2024.11.27 |
[JPA] 영속성 관리[2] (0) | 2024.11.26 |
[JPA] 영속성 관리[1] (0) | 2024.11.25 |