JPQL UPDATE와 비관적 락, 낙관적 락 차이: 동시성 문제를 실무처럼 고민하기
분야 프로그래밍/소프트웨어 개발
대상자
- Spring Data JPA를 사용하는 개발자
- 동시성 처리와 데이터 정합성 문제를 고민하는 개발자
- 낙관적 락과 비관적 락 전략 선택에 어려움을 겪는 개발자
핵심 요약
- JPQL UPDATE는 DB 레벨에서 비관적 락(Pessimistic Lock)을 자동으로 적용*합니다.
-
@Modifying(clearAutomatically = true)
는 영속성 컨텍스트를 초기화해 동시성 문제를 방지*합니다. - 비관적 락은 락을 즉시 적용해 정합성 보장*하지만 성능 저하가 발생합니다.
- 낙관적 락은 버전 필드(
@Version
) 기반 충돌 검사를 통해 성능 우위*를 제공합니다.
섹션별 세부 요약
1. 락 전략 비교
- 비관적 락 (Pessimistic Lock)
- JPQL UPDATE나
SELECT FOR UPDATE
로 DB 레벨에서 락 적용 - 동시 수정 방지, 데이터 정합성 강력 보장
- 성능 저하, 블로킹 발생 가능성
- 낙관적 락 (Optimistic Lock)
@Version
필드를 기반으로 충돌 검사- 성능 우수, 락 없이 동시성 처리
- 충돌 시 예외 발생, 로직 복잡도 증가
- 락 전략 선택 기준
- 데이터 변경 빈도, 트래픽 패턴, 동시성 중요도에 따라 선택
2. JPQL UPDATE의 특성
@Modifying
어노테이션과 함께 사용 시 쓰기 락(write lock) 자동 적용clearAutomatically = true
필수: 영속성 컨텍스트 초기화로 조회 결과 불일치 방지- 한 번의 쿼리로 리뷰 수, 평점 등 복합 필드 갱신 가능
- 예시 코드
```java
@Modifying(clearAutomatically = true)
@Query("""
UPDATE Book b
SET
b.reviewCount = (SELECT COUNT(r) FROM Review r WHERE r.book.id = :bookId),
b.rating = (SELECT COALESCE(AVG(r.rating), 0) FROM Review r WHERE r.book.id = :bookId)
WHERE b.id = :bookId
""")
void recalcStats(@Param("bookId") UUID bookId);
```
3. 실무 적용 고려사항
- 단순
++
연산은 동시성 충돌로 인해 중복 업데이트 또는 값 손실 발생 가능성 - 쿼리 기반 갱신과 락 전략 선택이 필수적 (예: 리뷰 생성 시 Book 엔티티 갱신)
- 비관적 락 대안:
LockModeType.PESSIMISTIC_WRITE
로 JPQL 없이 락 적용
```java
Book book = em.find(Book.class, bookId, LockModeType.PESSIMISTIC_WRITE);
```
- 참고 자료:
- Hibernate User Guide - Locking
- Spring Data JPA - Modifying Queries
- Baeldung - Optimistic Locking in JPA
결론
- 데이터 특성과 트래픽 패턴에 따라 적절한 락 전략 선택이 필수적
@Modifying(clearAutomatically = true)
는 JPQL UPDATE 시 동시성 문제를 방지하는 핵심 옵션- 비관적 락은 정합성 보장에 유리하지만 성능 저하 발생, 낙관적 락은 성능 우위를 가지나 충돌 시 로직 복잡도 증가
- 실무에서는 락 전략과 쿼리 기반 갱신을 병행해 동시성 문제를 최소화하는 것이 권장됨