본문 바로가기

JPA

[Spring Data JPA] JPA Hint & Lock

JPA Hint

JPA Hint는 Java Persistence API에서 쿼리 실행 시 추가적인 힌트를 제공하여 동작을 커스터마이즈하거나 성능 최적화를 돕는 기능

JPA 쿼리의 동작 방식을 유연하게 제어하거나, 특정 데이터베이스 벤더에 특화된 설정을 추가하는 데 사용된다. (SQL 힌트가 아니라 JPA 구현체에게 제공하는 힌트) 지금까지 JPA Hint의 뜻과 사용 용도를 알아보았고, 이제부터는 사용 방법에 대해서 알아보자.

 

쿼리 힌트 사용

 @QueryHints(value = @QueryHint(name = "org.hibernate.readOnly", value = "true"))
 Member findReadOnlyByUsername(String username);

 

쿼리 힌트 사용 확인

@Test
public void queryHint() {
    // given
    Member member1 = memberRepository.save(new Member("member1", 10));
    em.flush();
    em.clear();

    // when
    Member findMember = memberRepository.findReadOnlyByUsername(member1.getUsername()); // 스냅샷 X
    findMember.setUsername("member2");
    em.flush(); // Update Query 실행 X
}

 

쿼리 힌트 Page 추가 예제

@QueryHints(
        value = @QueryHint(name = "org.hibernate.readOnly", value = "true"),
        forCounting = true)
Page<Member> findPageByUsername(String username, Pageable pageable);
  • org.springframework.data.jpa.repository.QueryHints 어노테이션을 사용
  • forCounting : 반환 타입으로 `Page` 인터페이스를 적용하면 추가로 호출하는 페이징을 위한 count 쿼리도 쿼리 힌트 적용(기본값 `true` )

JPA LOCK

JPA는 두 가지 잠금 방식(Optimistic Locking Pessimistic Locking)을 제공한다.

 

1. Pessimistic Locking (비관적 잠금)

@Lock(LockModeType.PESSIMISTIC_WRITE)
List<Member> findLockByUsername(String username);
  • 데이터 충돌 가능성이 높을 것으로 가정하고, 데이터를 읽거나 수정할 때 다른 트랜잭션이 해당 데이터에 접근하지 못하도록 잠금을 건다.
  • org.springframework.data.jpa.repository.Lock 어노테이션을 사용

잠금 모드

 

  • PESSIMISTIC_READ: 다른 트랜잭션이 데이터를 읽을 수 있지만, 수정은 불가능.
  • PESSIMISTIC_WRITE: 다른 트랜잭션이 데이터를 읽거나 수정할 수 없음.
  • PESSIMISTIC_FORCE_INCREMENT: 데이터를 읽거나 수정할 때 버전 값도 강제로 증가.

 

Lock 사용 확인

@Test
    public void queryHint() {
        // given
        Member member1 = memberRepository.save(new Member("member1", 10));
        em.flush();
        em.clear();

        // when
        Member findMember = memberRepository.findReadOnlyByUsername(member1.getUsername()); // 스냅샷 X
        findMember.setUsername("member2");
        em.flush(); // Update Query 실행 X
    }

 

Lock 사용 결과

select
        m1_0.member_id,
        m1_0.age,
        m1_0.team_id,
        m1_0.username 
    from
        member m1_0 
    where
        m1_0.username=? for update

 

2. Optimistic Locking (낙관적 잠금)

@Entity
public class Member {

    @Id
    @GeneratedValue
    private Long id;

    @Version
    private int version; // Optimistic Locking에 사용되는 필드

    private String name;
}
  • 데이터 충돌 가능성이 낮을 것으로 가정하고, 데이터를 수정할 때 충돌 여부를 검사한다.
  • 데이터베이스에서 잠금을 유지하지 않으므로 성능이 높음.
  • 읽기 작업이 많은 환경에 적합하다.
  • 충돌이 발생하면 예외를 처리해야 하므로, 쓰기 작업이 많은 환경에서는 부적합하다.

작동 원리

// 데이터 수정 시 버전 값 확인
Member member = em.find(Member.class, memberId);
member.setName("New Name");
entityManager.merge(member);
// 버전 값이 일치하지 않으면 OptimisticLockException 발생
  • 엔티티에 버전 필드(예: @Version)를 추가한다.
  • 데이터를 읽을 때는 잠금을 걸지 않고, 데이터를 업데이트할 때 버전 값을 확인한다.
  • 만약 버전 값이 변경되었다면 예외를 발생시켜 충돌을 처리힌다.

정리

  • JPA에서 LOCK은 동시성 문제가 발생할 가능성이 있는 환경에서 데이터의 정합성을 보장하기 위해 사용된다.
  • Optimistic Locking은 성능이 중요한 경우에 적합하며, 데이터 충돌 처리가 필요하다.
  • Pessimistic Locking은 데이터 충돌을 사전에 방지하고자 할 때 유용하지만, 성능 저하와 데드락에 유의해야 한다.