2012-12-08 62 views
1

我在休眠中使用JSR-303驗證。我創建了一個自定義驗證器註釋,通過查詢數據庫來檢查數據完整性。使用休眠驗證時Hibernate會話被刷新

例如:

public boolean isValid(Object value, ConstraintValidatorContext context) { 
    if(value == null){ 
     return true; 
    } 
    //This method uses entityManager to fetch a list from database 
    Map<String, String> pickList = pickListProvider.getPickList(picklistName); 
    return pickList.contains(value); 
} 

用法:

public class UserProfile extends Persistent { 
    //Member fields come here 

    @PicklistConstraint(name="languages") 
    private String prefLanguage; 
} 

這裏用戶配置是一個實體持久使用休眠。 Hibernate在預插入或預先更新時調用此驗證。然而,當驗證器試圖從數據庫中提取記錄時,hibernate正在刷新會話。由於其上運行的驗證沒有完全出爐的域對象(不具備ID字段),我得到下面的異常

org.hibernate.AssertionFailure: null id in <domain object here> entry (don't flush the Session after an exception occurs) 
    at org.hibernate.event.internal.DefaultFlushEntityEventListener.checkId(DefaultFlushEntityEventListener.java:79) 
    at org.hibernate.event.internal.DefaultFlushEntityEventListener.getValues(DefaultFlushEntityEventListener.java:194) 
    at org.hibernate.event.internal.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:156) 
    at org.hibernate.event.internal.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:225) 
    at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:99) 
    at org.hibernate.event.internal.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:58) 
    at org.hibernate.internal.SessionImpl.autoFlushIfRequired(SessionImpl.java:1186) 
    at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1241) 
    at org.hibernate.internal.QueryImpl.list(QueryImpl.java:101) 
    at org.hibernate.ejb.QueryImpl.getResultList(QueryImpl.java:257) 

經調查我發現,DefaultAutoFlushEventListener的flushMightBeNeeded返回true:

private boolean flushMightBeNeeded(final EventSource source) { 
    return !source.getFlushMode().lessThan(FlushMode.AUTO) && 
      source.getDontFlushFromFind() == 0 && 
      (source.getPersistenceContext().getEntityEntries().size() > 0 || 
        source.getPersistenceContext().getCollectionEntries().size() > 0); 
} 

問題是FlushMode是AUTO和source.getPersistenceContext()。getEntityEntries()。size()有一些條目。 在這種情況下我的方法是什麼?我應該將FlushMode從AUTO更改爲MANUAL嗎?是否有可能獲得與實際實體不同的新會話或持久性上下文? 我正在使用spring mvc和hibernate。

編輯

我發現source.getPersistenceContext()。getEntityEntries()具有目前正在驗證的實體。插入前應該在這裏出現嗎?

+1

我不會使用hibernate驗證來做到這一點。它看起來像是應該在特定用例中檢查的業務規則,而不是每次實體持續或更新時必須執行的檢查(例如驗證給定字段不爲空或它是肯定的)。相反,我會明確檢查pickList是否包含該值,然後堅持該實體。 –

+0

在我看來,這不是一個商業規則。像性別這樣的領域可以有兩個值:「男」或「女」。這些值存儲在數據庫中,不會頻繁更改。這只是我們保留這些價值的問題。我可以使用屬性文件或硬編碼validator類中的值。所以這更像是數據完整性檢查。 – Vaibhav

+0

然後使用性別枚舉而不是字符串,和/或創建包含男性和女性的性別表,並在您的實體的性別列上添加外鍵約束。 –

回答

2

JPA規範說:

一般來說,便攜式應用的生命週期方法不應該調用EntityManager的 或查詢操作,訪問其它的實體實例或修改 相同的持久化上下文中的關係。

而且它也說:

自動校驗使用這些限制是通過指定的Java持久委託 驗證,於Bean驗證實施前的堅持,更新前完成,而預除去部分所述 實體生命週期事件3.5.2

所以規範明確地說,你不應該使用在一個生命週期事件的實體管理器,並自動驗證是使用生命週期事件執行的。所以,你不能在驗證器中使用實體管理器。

0

我已經添加了下面的代碼在我的驗證,似乎很好地工作:

public boolean isValid(Object value, ConstraintValidatorContext context) { 
    if(value == null){ 
     return true; 
    } 
    //This method uses entityManager to fetch a list from database 
    TransactionTemplate txTemplate = new TransactionTemplate(txManager);     
     txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); 
     Map<String, String> pickList = (Map<String, String>) txTemplate.execute(new TransactionCallback<Object>() { 
      public Object doInTransaction(TransactionStatus status) { 
       return pickListProvider.getPickList(picklistName); 
      } 
    }); 
    return pickList.contains(value); 
} 

我明白,這似乎違背了JPA規範,但我敢肯定的是,數據是這個驗證器獲取的數據是主數據的一部分,不會導致像幻影讀取這樣的隔離問題。