How can I make sure if the object already exist its not created again.
@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;
}
}
I did this way but as it is still keep creating new db entries instead of returning the new one.
While John's answer will work in most cases there is a multi threading issue, that might cause one call to fail, if two threads invoke getOrCreateProduct
simultaneously. Both threads might try to find an existing product and enter the NoResultException
block, if there is none. Then both will create a new product and try to merge it. On transaction.commit()
only one will succeed and the other thread will enter the PersistenceException
block.
This can be either handled by synchronizing your method (with impacts on performance) optionally with double checked locking or as you're already using spring, you could use spring's @Retryable
feature.
Here are examples for the different ways. All of the methods will be thread safe and working. But performance wise the getOrCreateProductWithSynchronization
will be worst as it synchronizes every call. The getOrCreateProductWithDoubleCheckedLocking
and getOrCreateProductWithRetryable
should almost be the same from a performance perspective. That is you have to decide whether to go with the additional code complexity introduced by the double-checked-locking or the usage of the spring-only @Retryable
feature.
@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:
One more thing to note. The implementations using synchronized
will only work correctly, if you got only one instance of your service. That is in a distributed setup these methods still might fail, if invoked in parallel on two ore more instances of your service. The @Retryable
solution will handle this correctly as well and thus should be the preferred solution.