2017-11-13 12 views
0

개체가 이미 만들어져 있는지 다시 확인할 수 있습니까?엔티티 관리자 가져 오기 또는 만들기

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

나는이 방법을 사용했으나 새 값을 반환하는 대신 새 db 항목을 계속 생성합니다.

+0

엔티티는 기본 키로 구별됩니다. 제품 이름 (또는 설명 또는 둘 다를 함께)이 PK 인 경우에만 중복을 피할 수 있습니다. –

+0

또한 PK가 항목 이름 인 경우 이미 DB에있는 제품에 대한 기존 제품 설명이 사용자의 접근 방식에 위배됩니다. –

답변

1

제품 이름, 제품 설명 또는 둘 모두가 Product 엔티티의 기본 키인 경우 접근 방식이 효과적이므로 PK가 다른 것, 아마도 대용 키라고 결론지었습니다. 기본적으로 사용하도록 도구로 제공됩니다. 제품 이름을 사용하여 새 영구 엔티티를 작성할지 또는 기존의 엔티티를 사용할 것인지 결정하려면 제품 이름에 대한 검색을 수행해야합니다. 예를 들면 다음과 같습니다.

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

해당 구현은 전혀 반환하지 않으면 "관리되는"엔터티를 반환합니다. 이것은 당신이 원하는 것일 수도 아닐 수도 있습니다. 분리 된 것을 원한다면 반환하기 전에 수동으로 분리 할 수 ​​있습니다 (이 경우, 새로운 것이면 먼저 플러시하는 것을 잊지 마십시오).

또한 제품 이름에 고유성 제약 조건을 설정하여이를 지원할 수도 있습니다.

+0

나는 당신의 접근 방식을 많이 좋아합니다. 나는 simular에 대해 생각하고있었습니다. 그리고 쿼리에 대한 업데이트에 대해서도 감사드립니다. 안타깝게도 Spring에서 공유 엔티티 관리자에서 트랜잭션을 생성 할 수 없다고 알려주고 메소드/클래스를 @transactional로 주석 처리하면 applicationContext.xml이 classpath를 찾을 수 없다고 표시합니다. 그거면 어디 있니? – digitalized

+0

나는 알아 냈다."@PersistenceContext"및 "@Autowired"EntityManagerFactory를 제거하고 대신 메소드에 새 entitymanager를 작성하고 매력처럼 작동했습니다. 당신의 도움을 주셔서 감사합니다. – digitalized

+0

다중 스레드를 고려하지 않으므로이 솔루션에는 문제가 있습니다. 나는. 두 스레드가'NoResultException' 블록을 입력하고 다른 스레드가 변경 사항을 커밋하기 전에 두 스레드가 모두 새로운 엔티티를 병합하려고합니다. 하나의 쓰레드는 성공할 것이고 다른 쓰레드는'PersistenceException' 블록에서 발견 될 것이다. 이러한 상황을 올바르게 처리해야하는 답변을 게시했습니다. – dpr

0

두 개의 스레드가 동시에 getOrCreateProduct을 호출하면 John의 응답이 대부분의 경우 작동하지만 하나의 호출이 실패 할 수있는 다중 스레드 문제가 있습니다. 두 스레드 모두 기존 제품을 찾고 블록이 없으면 NoResultException 블록을 입력 할 수 있습니다. 그런 다음 둘 다 새 제품을 만들고이를 병합하려고합니다. transaction.commit()에서 하나만 성공하고 다른 스레드는 PersistenceException 블록을 입력합니다.

두 가지 방법이 있습니다 (성능에 미치는 영향). 선택적으로 이중 잠금을 사용하거나 이미 스프링을 사용하고 있으므로 스프링의 @Retryable 기능을 사용할 수 있습니다.

다음은 다양한 방법의 예입니다. 모든 메소드는 스레드로부터 안전하고 작동합니다. 그러나 모든 전화를 동기화 할 때 성능이 좋으면 getOrCreateProductWithSynchronization은 최악입니다. getOrCreateProductWithDoubleCheckedLockinggetOrCreateProductWithRetryable은 성능 측면에서 거의 동일해야합니다. 즉, double-checked-locking 또는 스프링 전용 @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 
} 

UPDATE : 더 일에 주의합니다. synchronized을 사용하는 구현은 서비스 인스턴스가 하나 뿐인 경우 올바르게 작동합니다. 분산 설치에서 두 개 이상의 서비스 인스턴스에서 동시에 호출되는 경우 이러한 메소드가 여전히 실패 할 수 있습니다. @Retryable 솔루션도 올바르게 처리하므로 선호하는 솔루션이어야합니다.