AI 에이전트의 경우: 문서 인덱스는 https://www.mongodb.com/ko-kr/docs/llms.txt에서 사용할 수 있으며, 모든 페이지의 마크다운 버전은 어떤 URL 경로에 .md를 추가하여 사용할 수 있습니다.
See how MongoDB 9.0 delivers up to 2x higher throughput.
MongoDB Branding Shape
Register now >
Docs Menu

낙관적 잠금으로 업데이트 충돌 방지

이 가이드 에서는 최대 절전 모드 ORM용 MongoDB 확장을 사용하여 낙관적 잠금을 수행하는 방법을 학습 수 있습니다. 낙관적 잠금을 사용하면 동시 세션이 동일한 엔터티를 읽은 다음 둘 이상의 세션이 해당 엔터티에 쓰기 (write) 할 때 충돌을 감지합니다.

낙관적 잠금을 켜면 하이버네이트 ORM 확장은 각 업데이트 또는 삭제 작업에 대해 MongoDB 쿼리 언어(MQL) 성명서 생성합니다. 이 성명서 기본 키 외에도 세션에서 읽은 버전 값을 필터링합니다. 다른 세션이 문서 먼저 수정한 경우, 필터하다 와 일치하는 문서 없으며 최대 절전 모드 ORM은 OptimisticLockException을 발생시킵니다.

최대 절전 모드 ORM 확장은 비관적 잠금을 지원 하지 않습니다. 비관적 잠금은 세션이 엔터티를 읽을 때 잠그므로 세션이 락 해제할 때까지 다른 세션이 엔터티를 수정하지 못하도록 차단합니다.

엔터티에 대한 낙관적 잠금을 켜려면 엔터티 클래스에 @Version 주석이 달린 필드 추가합니다.

애플리케이션 버전 값을 읽는 경우 필드 에 대한 게터를 정의하지만 세터는 정의하지 않습니다. 하이버네이트 ORM은 값 자체를 할당하며, 애플리케이션 코드에서 필드 설정하면 하이버네이트 ORM 확장이 저장된 문서 와 일치하지 않는 필터하다 생성할 수 있습니다.

이 가이드 의 예시에서는 products 컬렉션 에 매핑되는 다음 Product 엔티티를 사용합니다. version 필드 초기화하지 마세요. 하이버네이트 ORM은 엔터티를 처음 유지할 때 초기값을 할당하기 때문입니다.

@Entity
@Table(name = "products")
public class Product {
@Id
@ObjectIdGenerator
private ObjectId id;
private String name;
private int quantity;
@Version
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 개인정보 정책에 Integer

  • long 개인정보 정책에 Long

  • Instant

숫자 버전 필드 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 유형을 사용합니다:

@Entity
@Table(name = "products")
@OptimisticLocking(type = OptimisticLockType.DIRTY)
@DynamicUpdate
public class Product {
@Id
@ObjectIdGenerator
private ObjectId id;
private String name;
private int quantity;
// Constructors, getters, and setters
}

버전이 지정된 엔터티의 낙관적 잠금에서 필드 제외하려면 필드 에 @OptimisticLock(excluded = true) 주석을 추가합니다. 제외된 필드 변경해도 버전이 증가하지 않습니다.

@Entity
@Table(name = "products")
public class Product {
@Id
@ObjectIdGenerator
private ObjectId id;
@OptimisticLock(excluded = true)
private String name;
private int quantity;
@Version
private Long version;
// Constructors, getters, and setters
}

낙관적 잠금에는 다음과 같은 제한 사항이 있습니다.

  • 최대 절전 모드 ORM 확장은 비관적 잠금을 지원 하지 않습니다. 잠금 지원 에 대해 자세히 학습 기능 호환성 페이지의 트랜잭션 및 동시성 섹션을 참조하세요.

  • @Version 필드 있는 엔터티를 StatelessSession.upsert() 메서드에 전달할 수 없습니다. 하이버네이트 ORM 확장은 이 작업에 대해 UnsupportedFeatureException를 발생시킵니다.

낙관적 잠금에 대해 자세히 학습 최대 절전 모드 ORM 문서에서 낙관적 잠금을 참조하세요.

트랜잭션 에서 쓰기 (write) 작업을 실행 방법에 대해 자세히 학습 트랜잭션 및 세션 가이드 참조하세요.