본문 바로가기

Spring Data JPA

Hint & Lock

 

실전! 스프링 데이터 JPA - 인프런 | 강의

스프링 데이터 JPA는 기존의 한계를 넘어 마치 마법처럼 리포지토리에 구현 클래스 없이 인터페이스만으로 개발을 완료할 수 있습니다. 그리고 반복 개발해온 기본 CRUD 기능도 모두 제공합니다.

www.inflearn.com

 

Spring Data JPA - Reference Documentation

Example 119. Using @Transactional at query methods @Transactional(readOnly = true) interface UserRepository extends JpaRepository { List findByLastname(String lastname); @Modifying @Transactional @Query("delete from User u where u.active = false") void del

docs.spring.io

 

JPA의 낙관적 잠금(Optimistic Lock), 비관적 잠금(Pessimistic Lock)

요청이 많은 서버에서 여러 트랜잭션이 동시에 같은 데이터에 업데이트를 발생시킬 경우에 일부 요청이 유실되는 경우가 발생하여 장애로 이어질 수 있습니다. 이를 위해 동시 읽기/업데이트

velog.io

 

JPA에서 Optimistic Lock과 Pessimistic Lock

낙관적 락 (Optimistic Lock) 트랜잭션 대부분 충돌이 발생하지 않는다고 가정하는 방법으로써 어플리케이션이 제공하는 락 방식입니다. 그러므로 읽는 시점에 Lock을 사용하지 않기 때문에 데이터를

skasha.tistory.com

 

JPA 잠금(Lock) 이해하기

JPA(Hibernate:하이버네이트)에 의한 잠금(Lock:락) 사용중에 생각하고 있던바와 동작이 좀 다른 부분이 있어서 전반적으로 정리해 보았습니다. 잠금(Lock)의 종류 낙관적 잠금(Optimisstic Lock) 낙관적 잠

reiphiel.tistory.com

HINT

JPA 힌트는 SQL 힌트가 아니라 JPA 구현체에게 제공하는 힌트이다.

JPA는 쿼리 최적화를 위한 SQL힌트는 제공하고 있지 않으며 SQL힌트를 사용하기 위해서는 NativeQuery를 사용해야 한다.

@QueryHints 어노테이션과 @QueryHint 어노테이션을 사용하여 구현한다.

  • org.springframework.data.jpa.repository.QueryHints
  • javax.persistence.QueryHint

readOnly

기본적으로 JPA는 영속성컨텍스트의 변경감지를 통해 CUD 쿼리를 처리한다.

이를 위해서 영속성 컨텍스트는 항상 원본 엔티티 객체와 관리를 위한 사본 엔티티 객체를 총 두 개의 객체를 메모리에 저장한다.

엔티티를 읽기만 할 때는 원본객체가 필요없기 때문에 readOnly 힌트를 통해 원본 객체를 저장하지 않도록하여 성능 최적화를 할 수 있다.

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

forCounting 속성

반환타입으로 Page인터페이스를 적용할 때 페이징을 위해 추가로 호출하는 count 쿼리에도 힌트를 적용한다. (default : true)

@QueryHints(value = { @QueryHint(name = "org.hibernate.readOnly", value = "true")},
            forCounting = true)
Page<Member> findByUsername(String name, Pagable pageable);

 

LOCK

JPA의 잠금에는 낙관적 잠금(Optimistic Lock)과 비관적 잠금(Pessimistic Lock)이 있다.

낙관적 잠금(Optimistic Lock)

  • 어플리케이션 단에서 제공하는 락 방식으로 데이터 갱신시 충돌이 발생하지 않을 것이라 가정하고 잠금을 거는 기법이다.
  • DB Lock을 사용하지 않기 때문에 트랜잭션 커밋 전까지 충돌 여부를 알 수 없고 갱신시점에 데이터의 변경여부를 확인해야한다.
  • 디비에 락을 걸기보다는 충돌 방지(Conflict detection)에 가깝다고 볼 수 있다.

@Version

  • JPA에서 낙관적 락을 사용하려면 엔티티 내부에 @Version 어노테이션을 사용한 필드를 추가하여 버전 관리를 구현하면 된다.
  • 엔티티에는 하나의 버전 필드만 있어야하며 지원되는 타입은 long, Long, int, Integer, short, Short, TimeStamp 이다.
  • 버전필드는 엔티티를 수정 할 때 자동으로 증가하며, 조회 시점과 버전이 다른 경우 OptimisticLockException이 발생한다.

낙관적 잠금 종류

  • NONE
    엔티티에 버전 필드이 존재하면 별도의 옵션을 적용하지 않아도 암시적으로 낙관적 잠금이 적용된다.
  • OPTIMISTIC (Read)
    엔티티 수정 시에만 발생하는 낙관적 잠금을 읽기 시에도 발생하도록 설정한다.
    이를 통해 dirty read와 non-repeatable read를 방지한다.
  • OPTIMISTIC_FORCE_INCREMENT (Write)
    낙관적 잠금을 사용하면서 버전 정보를 강제로 증가시킨다.

기본 JPA에서의 명시적 낙관적 잠금 사용방법

// 엔티티를 영속성 컨텍스트로 부터 찾거나 select하면서 동시에 잠금을 거는 경우 사용
entityManager.find(Member.class, memberId, LockModeType.OPTIMISTIC);
Member member = entityManager.find(Member.class, memberNo);
// 이미 영속성 컨텍스트에 담겨있는 엔터티를 대상으로 잠금을 걸때 사용
entityManager.lock(member, LockModeType.OPTIMISTIC);

Query객체의 setLockMode() 메서드를 이용한 방법

Query query = entityManager.createQuery("select m from Member m where m.id = :id");
query.setParameter("id", memberId);
query.setLockMode(LockModeType.OPTIMISTIC_INCREMENT);
query.getResultList()

@NamedQuery 어노테이션의 lockMode 속성을 이용한 방법

@NamedQuery(name="optimisticLock", 
  query="select m from Member m where m.id = :id",
  lockMode = WRITE)

비관적 잠금(Pessimistic Lock)

  • 트랜잭션 충돌이 발생한다고 가정하고 우선 락을 거는 방법이다.

비관적 잠금 종류

  • LockModeType.PESSIMISTIC_WRITE (default)
    배타적 잠금(Exclusive Lock)
    다른 트랜잭션에서 READ, UPDATE, DELETE가 불가능하도록 설정한다.
  • LockModeType.PESSIMISTIC_READ
    공유 잠금(Shared Lock)
    다른 트랙잭션에서 READ 만 가능하도록 설정한다.
  • LockModeType.PESSINISTIC_FORCE_INCREMENT
    PESSIMISTIC_WRITE와 유사하게 작동하지만 엔티티의 버전 필드를 추가로 증가시킨다.

Spring Data JPA에서의 비관적 잠금 사용방법

// JPA의 기본 동작은 select - update 이다
// 여러 쓰레드에서 동시에 하나의 값을 변경하게 되면 정합성을 보장하기 어렵기 때문에 비관적 잠금을 설정해줄 수 있다.
@Lock(LockModeType.PESSIMISTIC_WRITE)
List<Member> findByUsername(String name);

Exception

PessimisticLockException

  • 공유잠금(Shared Lock) 또는 배타적잠금(Exclusive Lock) 둘중에 하나의 락만 획득 할 수 있다.
  • 락을 획득하는데 실패하면 해당 예외가 발생한다.

LockTimeoutException

  • 락을 대기하다 설정된 wait time이 초과되었을때 발생한다.

PersistenceException

  • 영속성 문제가 발생하는 경우 발생하는 예외

Lock Scope

PessimisticLockScope.NORMAL (default)

  • 해당 엔티티만 잠금을 설정한다.
  • @Inheritance(strategy = InheritanceType.JOINED)와 같이 조인 상속을 사용하면 부모도 함께 잠금이 설정된다.

PessimisticLockScope.EXTENDED

  • @ElementCollection, @OneToOne, @OneToMany 등 연관관계에 있는 엔티티도 함께 잠금이 설정된다.

 

'Spring Data JPA' 카테고리의 다른 글

Auditing  (0) 2022.12.20
사용자 정의 레퍼지토리  (0) 2022.12.18
엔티티 그래프 @EntityGraph  (0) 2022.12.18
벌크성 수정쿼리 @Modifying  (0) 2022.12.18
페이징과 정렬  (0) 2022.12.18