개요
이 가이드 에서는 최대 절전 모드 ORM용 MongoDB 확장을 사용하여 낙관적 잠금을 수행하는 방법을 학습 수 있습니다. 낙관적 잠금을 사용하면 동시 세션이 동일한 엔터티를 읽은 다음 둘 이상의 세션이 해당 엔터티에 쓰기 (write) 할 때 충돌을 감지합니다.
낙관적 잠금을 켜면 하이버네이트 ORM 확장은 각 업데이트 또는 삭제 작업에 대해 MongoDB 쿼리 언어(MQL) 성명서 생성합니다. 이 성명서 기본 키 외에도 세션에서 읽은 버전 값을 필터링합니다. 다른 세션이 문서 먼저 수정한 경우, 필터하다 와 일치하는 문서 없으며 최대 절전 모드 ORM은 OptimisticLockException을 발생시킵니다.
최대 절전 모드 ORM 확장은 비관적 잠금을 지원 하지 않습니다. 비관적 잠금은 세션이 엔터티를 읽을 때 잠그므로 세션이 락 해제할 때까지 다른 세션이 엔터티를 수정하지 못하도록 차단합니다.
버전 필드 추가
엔터티에 대한 낙관적 잠금을 켜려면 엔터티 클래스에 @Version 주석이 달린 필드 추가합니다.
애플리케이션 버전 값을 읽는 경우 필드 에 대한 게터를 정의하지만 세터는 정의하지 않습니다. 하이버네이트 ORM은 값 자체를 할당하며, 애플리케이션 코드에서 필드 설정하면 하이버네이트 ORM 확장이 저장된 문서 와 일치하지 않는 필터하다 생성할 수 있습니다.
이 가이드 의 예시에서는 products 컬렉션 에 매핑되는 다음 Product 엔티티를 사용합니다. version 필드 초기화하지 마세요. 하이버네이트 ORM은 엔터티를 처음 유지할 때 초기값을 할당하기 때문입니다.
public class Product { private ObjectId id; private String name; private int quantity; private Long version; public Product() { } public Product(String name, int quantity) { this.name = name; this.quantity = quantity; } public ObjectId getId() { return id; } public Long getVersion() { return version; } public void setQuantity(int quantity) { this.quantity = quantity; } }
최대 절전 모드 ORM 확장은 @Version 필드 대해 다음 유형을 지원합니다.
int개인정보 정책에Integerlong개인정보 정책에LongInstant
숫자 버전 필드 0 에서 시작하여 쓰기 (write) 마다 1씩 증가합니다. Instant 버전 필드 쓰기 (write) 시간을 저장합니다.
버전이 지정된 엔터티 업데이트
새 엔터티를 유지할 때 Hibernate ORM은 버전 필드 0로 설정합니다. 그런 다음 엔터티를 수정하고 트랜잭션 커밋 하이버네이트 ORM 확장은 기본 키 와 세션에서 읽은 버전 값을 모두 필터링하는 업데이트 성명서 생성합니다. 이 성명서 새 버전 값도 설정합니다.
다음 예시 새 Product 엔터티를 삽입한 다음 해당 quantity 필드 업데이트하여 버전을 증가시킵니다.
var sf = HibernateUtil.getSessionFactory(); try (Session session = sf.openSession()) { // Insert a new product, which sets the version to 0 session.beginTransaction(); var product = new Product("notebook", 100); session.persist(product); session.getTransaction().commit(); System.out.println("Version after insert: " + product.getVersion()); // Update the same product, which increments the version to 1 session.beginTransaction(); product.setQuantity(75); session.getTransaction().commit(); System.out.println("Version after update: " + product.getVersion()); } sf.close();
이전 예시 의 업데이트 다음 MQL 과 유사한 성명서 생성합니다.
{ "update": "products", "updates": [ { "q": { "$and": [ { "_id": { "$eq": "..." } }, { "version": { "$eq": 0 } } ] }, "u": { "$set": { "quantity": 75, "version": 1 } } } ] }
삭제 작업은 동일한 필터하다 사용합니다. 버전 값은 필터하다 의 일부이므로 문서 세션에서 읽은 버전을 여전히 보유하고 있는 경우에만 쓰기 (write) 성공합니다.
버전 충돌 처리
세션이 문서를 읽은 후 다른 세션에서 문서 수정하면 필터하다 의 버전 값이 더 이상 저장된 값과 일치하지 않으며 하이버네이트 ORM이 OptimisticLockException을 발생시킵니다. 복구하려면 예외를 포착하고 엔터티를 다시 로드하고 쓰기 (write) 다시 시도하거나 애플리케이션 사용자에게 충돌을 알리는 오류를 반환합니다.
다음 예시 두 개의 세션을 사용하여 동일한 문서 읽습니다. 두 번째 세션이 먼저 커밋되므로 첫 번째 세션의 커밋 은 OptimisticLockException을 발생시킵니다.
var sf = HibernateUtil.getSessionFactory(); ObjectId productId; // Insert a new product for two sessions to modify try (Session session = sf.openSession()) { session.beginTransaction(); var product = new Product("notebook", 100); session.persist(product); session.getTransaction().commit(); productId = product.getId(); } try (Session sessionA = sf.openSession(); Session sessionB = sf.openSession()) { // Both sessions load the product at version 0 var productA = sessionA.find(Product.class, productId); var productB = sessionB.find(Product.class, productId); // Session B commits first and increments the version to 1 sessionB.beginTransaction(); productB.setQuantity(75); sessionB.getTransaction().commit(); // Session A commits second, but still holds version 0 try { sessionA.beginTransaction(); productA.setQuantity(50); sessionA.getTransaction().commit(); } catch (OptimisticLockException e) { System.out.println("Update failed: Another session modified this document"); // Reload the entity and retry, or report the conflict to the user } } sf.close();
참고
애플리케이션 엔터티를 로드하는 방법에 따라 Hibernate ORM은 대신 StaleObjectStateException을 발생시킬 수 있습니다. 이 예외는 OptimisticLockException에 해당하는 하이버네이트 ORM 네이티브 예외입니다.
데이터를 변경하지 않고 버전 증가
세션이 수정하지 않은 엔터티의 버전을 증가시키려면 엔터티와 LockMode.OPTIMISTIC_FORCE_INCREMENT 락 모드 Session의 lock() 메서드에 전달합니다. 관련 데이터를 변경하여 다른 세션의 엔터티 복사본을 무효화해야 하는 경우 이 락 모드 사용합니다.
다음 예시 Product 문서 의 버전을 증가시킵니다.
var sf = HibernateUtil.getSessionFactory(); try (Session session = sf.openSession()) { session.beginTransaction(); var product = session.createQuery("from Product where name = :n", Product.class) .setParameter("n", "notebook") .setMaxResults(1) .getSingleResult(); session.lock(product, LockMode.OPTIMISTIC_FORCE_INCREMENT); session.getTransaction().commit(); } sf.close();
다른 세션이 먼저 버전을 증가시키면 커밋 OptimisticLockException을 발생시킵니다.
버전 필드 없이 잠금
버전 필드 추가하지 않고도 충돌을 감지할 수도 있습니다. 이렇게 하려면 엔터티에 @OptimisticLocking 주석을 달고 다음 유형 중 하나를 지정합니다:
OptimisticLockType.DIRTY: 하이버네이트 ORM 확장은 세션이 수정한 필드의 이전 값만 필터링합니다.OptimisticLockType.ALL: 하이버네이트 ORM 확장은 모든 엔터티 필드의 이전 값을 필터링합니다.
두 유형 모두 @DynamicUpdate 주석이 필요합니다.
다음 예시 DIRTY 유형을 사용합니다:
public class Product { private ObjectId id; private String name; private int quantity; // Constructors, getters, and setters }
버전이 지정된 엔터티의 낙관적 잠금에서 필드 제외하려면 필드 에 @OptimisticLock(excluded = true) 주석을 추가합니다. 제외된 필드 변경해도 버전이 증가하지 않습니다.
public class Product { private ObjectId id; private String name; private int quantity; private Long version; // Constructors, getters, and setters }
제한 사항
낙관적 잠금에는 다음과 같은 제한 사항이 있습니다.
@Version필드 있는 엔터티를StatelessSession.upsert()메서드에 전달할 수 없습니다. 하이버네이트 ORM 확장은 이 작업에 대해UnsupportedFeatureException를 발생시킵니다.
추가 정보
낙관적 잠금에 대해 자세히 학습 최대 절전 모드 ORM 문서에서 낙관적 잠금을 참조하세요.
트랜잭션 에서 쓰기 (write) 작업을 실행 방법에 대해 자세히 학습 트랜잭션 및 세션 가이드 참조하세요.