如何确定对象是否已经存在,而不是再次创建它。
@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;
}
}
我这样做了,但因为它仍在继续创建新的数据库条目而不是返回新的数据库。
答案 0 :(得分:4)
虽然John的答案在大多数情况下都有效但有一个多线程问题,如果两个线程同时调用getOrCreateProduct
,可能会导致一个调用失败。两个线程都可能尝试查找现有产品并输入NoResultException
块(如果没有)。然后两者都将创建一个新产品并尝试合并它。在transaction.commit()
上只有一个会成功,另一个线程将进入PersistenceException
块。
这可以通过同步您的方法(对性能的影响)来处理,也可以选择双重检查锁定,或者当您已经使用spring时,可以使用spring的@Retryable
功能。 / p>
以下是不同方式的示例。所有方法都是线程安全且有效的。但是性能明智的getOrCreateProductWithSynchronization
将是最差的,因为它同步每个呼叫。从绩效角度来看,getOrCreateProductWithDoubleCheckedLocking
和getOrCreateProductWithRetryable
应该几乎相同。那就是你必须决定是否采用双重检查锁定引入的额外代码复杂性或仅使用spring @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
解决方案也将正确处理,因此应该是首选解决方案。
答案 1 :(得分:0)
由于如果产品名称,产品描述或两者一起是Product
实体的主键,您的方法应该有效,我得出结论,PK是其他东西 - 可能是代理键,因为它和#39; 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;
}
请注意,该特定实现会返回&#34;托管&#34;实体,如果它返回一个。这可能是也可能不是你想要的。如果你想要一个分离的,那么你可以在返回之前手动分离它(但在这种情况下,如果它是新的,不要忘记先将它冲洗掉。)
您可能还希望通过在产品名称上设置唯一性约束来支持此功能。