实体管理器获取或创建

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;
    }
}

我是这样做的,但它仍然继续创建新的数据库条目,而不是返回新的条目。

由于如果产品名称、产品描述或两者一起是 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;
    }

请注意,该特定实现 returns 是一个 "managed" 实体,如果它 returns 一个的话。这可能是也可能不是您想要的。如果你想要一个分离的,那么你当然可以在返回之前手动分离它(但在这种情况下,如果它是新的,不要忘记先冲洗它)。

您可能还想通过对产品名称设置唯一性约束来支持这一点。

虽然 John 的回答在大多数情况下都有效,但存在一个多线程问题,如果两个线程同时调用 getOrCreateProduct,这可能会导致一次调用失败。如果有 none,两个线程都可能会尝试查找现有产品并进入 NoResultException 块。然后两者都会创建一个新产品并尝试合并它。在 transaction.commit() 只有一个会成功,另一个线程会进入 PersistenceException 块。

这可以通过同步您的方法(对性能有影响)来处理,可选地使用双重检查锁定,或者因为您已经在使用 spring,您可以使用 spring 的 @Retryable 特征。

以下是不同方式的示例。所有的方法都是线程安全的并且可以工作。但是性能方面 getOrCreateProductWithSynchronization 会最差,因为它会同步每个调用。 getOrCreateProductWithDoubleCheckedLockinggetOrCreateProductWithRetryable 从性能的角度来看应该几乎相同。也就是说,您必须决定是使用双重检查锁定引入的额外代码复杂性,还是使用 spring-only @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 解决方案也能正确处理这个问题,因此应该是首选解决方案。