Search code examples
javaspringhibernate-entitymanager

Entity manager get or create


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.


Solution

  • 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.