[英]Entity manager get or create
如何確定對象是否已經存在,不會再次創建。
@Component
public class ProductServiceImpl implements ProductService
{
@PersistenceContext
private EntityManager em;
public Product getOrCreateProduct(String productName, String peoductDescr)
{
Product product =(new Product (productName, peoductDescr));
em.merge(product);
return product;
}
}
我這樣做是因為它仍然繼續創建新的數據庫條目,而不是返回新的數據庫條目。
盡管John的回答在大多數情況下都可以解決,但存在多線程問題,但如果兩個線程同時調用getOrCreateProduct
,則可能導致一個調用失敗。 如果沒有,這兩個線程都可能嘗試查找現有產品並輸入NoResultException
塊。 然后兩者都將創建一個新產品並嘗試將其合並。 在transaction.commit()
只有一個成功,另一個線程將進入PersistenceException
塊。
這可以通過將您的方法(對性能@Retryable
)同步(可選地)與雙重檢查鎖定進行處理,也可以在已經使用spring的情況下使用spring的@Retryable
功能進行處理。
以下是不同方式的示例。 所有方法都是線程安全的並且可以正常工作。 但是在性能方面,由於getOrCreateProductWithSynchronization
會同步每個調用,因此性能最差。 從性能的角度來看, getOrCreateProductWithDoubleCheckedLocking
和getOrCreateProductWithRetryable
應該幾乎相同。 那就是您必須決定是否要使用由雙重檢查鎖定引入的額外代碼復雜性,還是要使用僅彈簧的@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
}
更新:還有一點要注意。 如果只有服務的一個實例,則使用synchronized
的實現將只能正常工作。 也就是說,在分布式設置中,如果在服務的兩個或多個實例上並行調用這些方法,則這些方法仍可能會失敗。 @Retryable
解決方案也將正確處理此問題,因此應該是首選解決方案。
因為如果產品名稱,產品描述或兩者一起是Product
實體的主鍵,那么您的方法應該可以工作,因此我得出結論,PK是其他東西-可能是替代鍵,因為這是JPA所使用的工具默認。 如果要使用產品名稱來決定是創建一個新的持久性實體還是使用現有的實體,則需要對產品名稱進行搜索。 這樣的事情,例如:
public Product getOrCreateProduct(String productName, String productDescr) {
Product product;
try {
TypedQuery<Product> productForName = em.createQuery(
"select p from Product p where p.name = ?1", Product.class);
EntityTransaction transaction;
productForName.setParameter(1, productName);
/*
* The query and any persist() operation required should be
* performed in the same transaction. You might, however, want
* to be a little more accommodating than this of any transaction
* that is already in progress.
*/
transaction = em.getTransaction();
transaction.begin();
try {
product = productForName.getSingleResult();
} catch (NoResultException nre) {
product = new Product(productName, productDescr);
em.persist(product);
}
transaction.commit();
} catch (PersistenceException pe) {
// ... handle error ...
}
return product;
}
請注意,如果特定實現完全返回一個“托管”實體,則它返回一個“托管”實體。 這可能是您想要的,也可能不是。 如果您想要分離的,則可以在返回之前手動分離它(但是在這種情況下,如果是新的,別忘了先沖洗它)。
您可能還想通過在產品名稱上設置唯一性約束來支持這一點。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.