두 개의 스레드가 동시에 getOrCreateProduct
을 호출하면 John의 응답이 대부분의 경우 작동하지만 하나의 호출이 실패 할 수있는 다중 스레드 문제가 있습니다. 두 스레드 모두 기존 제품을 찾고 블록이 없으면 NoResultException
블록을 입력 할 수 있습니다. 그런 다음 둘 다 새 제품을 만들고이를 병합하려고합니다. transaction.commit()
에서 하나만 성공하고 다른 스레드는 PersistenceException
블록을 입력합니다.
두 가지 방법이 있습니다 (성능에 미치는 영향). 선택적으로 이중 잠금을 사용하거나 이미 스프링을 사용하고 있으므로 스프링의 @Retryable
기능을 사용할 수 있습니다.
다음은 다양한 방법의 예입니다. 모든 메소드는 스레드로부터 안전하고 작동합니다. 그러나 모든 전화를 동기화 할 때 성능이 좋으면 getOrCreateProductWithSynchronization
은 최악입니다. getOrCreateProductWithDoubleCheckedLocking
및 getOrCreateProductWithRetryable
은 성능 측면에서 거의 동일해야합니다. 즉, double-checked-locking 또는 스프링 전용 @Retryable
기능의 사용으로 인한 추가 코드 복잡성을 사용할지 여부를 결정해야합니다.
@Transactional(propagation = Propagation.REQUIRES_NEW)
public synchronized Product getOrCreateProductWithSynchronization(final String productName, final String productDescr) {
Product product = findProduct(productName);
if (product != null) {
return product;
}
product = new Product(productName, productDescr);
em.persist(product);
return product;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Product getOrCreateProductWithDoubleCheckedLocking(final String productName, final String productDescr) {
Product product = findProduct(productName);
if (product != null) {
return product;
}
synchronized (this) {
product = findProduct(productName);
if (product != null) {
return product;
}
product = new Product(productName, productDescr);
em.persist(product);
}
return product;
}
@Retryable(include = DataIntegrityViolationException.class, maxAttempts = 2)
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Product getOrCreateProductWithRetryable(final String productName, final String productDescr) {
Product product = findProduct(productName);
if (product != null) {
return product;
}
product = new Product(productName, productDescr);
em.persist(product);
return product;
}
private Product findProduct(final String productName) {
// try to find an existing product by name or return null
}
UPDATE : 더 일에 주의합니다. synchronized
을 사용하는 구현은 서비스 인스턴스가 하나 뿐인 경우 올바르게 작동합니다. 분산 설치에서 두 개 이상의 서비스 인스턴스에서 동시에 호출되는 경우 이러한 메소드가 여전히 실패 할 수 있습니다. @Retryable
솔루션도 올바르게 처리하므로 선호하는 솔루션이어야합니다.
출처
2017-11-14 09:46:32
dpr
엔티티는 기본 키로 구별됩니다. 제품 이름 (또는 설명 또는 둘 다를 함께)이 PK 인 경우에만 중복을 피할 수 있습니다. –
또한 PK가 항목 이름 인 경우 이미 DB에있는 제품에 대한 기존 제품 설명이 사용자의 접근 방식에 위배됩니다. –