2013-01-15 75 views
1

我有一個簡單的Hibernate實體:休眠:ConstraintViolationException並行插入

@Entity 
@Table(name = "keyword", 
     uniqueConstraints = @UniqueConstraint(columnNames = { "keyword" })) 
public class KeywordEntity implements Serializable { 

    private Long id; 
    private String keyword; 

    public KeywordEntity() { 
    } 

    @Id 
    @GeneratedValue 
    @Column(unique = true, updatable=false, nullable = false) 
    public Long getId() { 
     return this.id; 
    } 

    public void setId(Long id) { 
     this.id = id; 
    } 

    @Column(name="keyword") 
    public String getKeyword() { 
     return this.keyword; 
    } 

    public void setKeyword(String keyword) { 
     this.keyword = keyword; 
    } 
} 

DAO吧:

@Component 
@Scope("prototype") 
public class KeywordDao { 

    protected SessionFactory sessionFactory; 

    @Autowired 
    public void setSessionFactory(SessionFactory sessionFactory) { 
     this.sessionFactory = sessionFactory; 
    } 

    public KeywordEntity findByKeyword(String keyword) throws NotFoundException { 
     Criteria criteria = sessionFactory.getCurrentSession() 
       .createCriteria(KeywordEntity.class) 
       .add(Restrictions.eq("keyword", keyword)); 
     KeywordEntity entity = (KeywordEntity) criteria.uniqueResult(); 
     if (entity == null) { 
      throw new NotFoundException("Not found"); 
     } 
     return entity; 
    } 

    public KeywordEntity createKeyword(String keyword) { 
     KeywordEntity entity = new KeywordEntity(keyword); 
     save(entity); 
     return entity; 
    } 
} 

和服務,這使@Transactional下的一切:

@Repository 
@Scope("prototype") 
public class KeywordService { 

    @Autowired 
    private KeywordDao dao; 

    @Transactional(readOnly = true) 
    public KeywordEntity getKeyword(String keyword) throws NotFoundException { 
     return dao.findByKeyword(keyword); 
    } 

    @Transactional(readOnly = false) 
    public KeywordEntity createKeyword(String keyword) { 
     return dao.createKeyword(keyword); 
    } 

    @Transactional(readOnly = false) 
    public KeywordEntity getOrCreateKeyword(String keyword) { 
     try { 
      return getKeyword(keyword); 
     } catch (NotFoundException e) { 
      return createKeyword(keyword); 
     } 
    } 
} 

在單線程環境中,此代碼運行得很好。我在多線程環境中使用它時遇到了問題。當有多個並行線程時,使用相同的關鍵字,其中一些使用相同的關鍵字同時調用getOrCreateKeyword,並且發生以下情況:

2個線程同時使用相同的關鍵字調用關鍵字服務,兩者都先嚐試獲取現有的關鍵字,但都沒有找到,並且都嘗試創建新的關鍵字。第一個成功,第二個 - 導致ConstraintViolationException被拋出。

所以,我曾嘗試提高getOrCreateKeyword方法一點:

@Transactional(readOnly = false) 
public KeywordEntity getOrCreateKeyword(String keyword) { 
    try { 
     return getKeyword(keyword); 
    } catch (NotFoundException e) { 
     try { 
      return createKeyword(keyword); 
     } catch (ConstraintViolationException ce) { 
      return getKeyword(keyword); 
     } 
    } 
} 

所以理論上它應該解決的問題,但在實踐中,一旦ConstraintViolationException被拋出,調用getKeyword(keyword)導致另一個Hibernate的異常:

AssertionFailure - an assertion failure occured (this may indicate a bug in Hibernate, 
but is more likely due to unsafe use of the session)org.hibernate.AssertionFailure: 
null id in KeywordEntity entry (don't flush the Session after an exception occurs) 

如何解決這個問題?

回答

1

解決方案是在ConstraintViolationException發生時丟棄當前會話,並在新會話中再次檢索關鍵字。 Hibernate Documentation也指向此:

如果會話引發異常,則必須回滾事務並丟棄會話。發生異常後,會話的內部狀態可能與數據庫不一致。

2

您可以使用某種使用數據庫/休眠的悲觀鎖定機制,或者如果您在單臺計算機上運行,​​則可以使服務方法getOrCreateKeyword()同步。

這裏有一些參考。

Hibernate的文檔http://docs.jboss.org/hibernate/core/3.3/reference/en/html/transactions.html#transactions-locking

本文介紹瞭如何把一個特定實體的鎖,所有的實體從查詢它可以幫助你的結果。 http://www.objectdb.com/java/jpa/persistence/lock#Locking_during_Retrieval_

+0

這是一個多機環境,所以'synchronized'不會幫助...你能指點我一些關於悲觀鎖機制的資源嗎? – Laimoncijus

+0

我發現了兩篇文章。第一個是冬眠文檔,第二個來自objectdb顯示瞭如何從查詢結果中鎖定特定實體和所有實體,這可能會對您有所幫助。 http://docs.jboss.org/hibernate/core/3.3/reference/en/html/transactions.html#transactions-locking http://www.objectdb.com/java/jpa/persistence/lock –