[英]Is there an easier way to tell that a JPA Entity has been modified other than looping through each field?
我有一個自定義的EmptyInterceptor ,可用於通過覆蓋onSave
和onFlushDirty
來設置有關創建日期,上次修改日期,用戶創建和上次修改用戶的onFlushDirty
。
這個系統對我們來說運作良好,但是我們發現一個問題,就是我們的代碼使用一些輸入數據盲目地調用實體上的設置器。 在這種情況下,即使數據沒有更改,休眠也會觸發我們的自定義攔截器,並設置最后修改的用戶和日期。 這將導致Hibernate更新數據庫中的實體。 我們知道這一點是因為我們在此表上有一個插入和更新觸發器。 如果禁用攔截器,則Hibernate將不會更新數據庫中的實體。
如果實體沒有真正更改,我們希望避免設置用戶和日期,因此在這種情況下不會發生任何更新。 我已經了解了Hibernate進行臟實體檢查的方式,並且已經將previousState
和currentState
數組傳遞到onFlushDirty
,我可以循環執行此檢查。 有沒有更簡單的方法可以做到這一點?
我查看了HibernateSession.isDirty(),但這並沒有告訴我這個特定的實體是否已更改,即使會話中有更改的實體也是如此。
更新
事實證明,有問題的代碼盲目地調用setter並不是問題。 設置子對象的集合而不是修改已經存在的集合是令人討厭的代碼。 在這種情況下,Hibernate認為該實體已發生更改-或至少認為足以調用Interceptor。
從設計的角度來看,該檢查實際上應該在前端/客戶端進行。 如果前端確定該記錄已由用戶修改,則它應將更新提交給服務器。
如果要在中間層(服務器端)執行此操作,則應考慮Hibernate實體的生命周期:瞬態,持久性,分離,已刪除,還應考慮session.save()
與session.merge()
有何不同session.merge()
和session.saveOrUpdate()
。
此外,在管理會話時,還必須考慮不同的設計模式,例如“每次操作會話” 反模式 ,每次請求會話或每次會話會話模式等。
如果您有一個打開的會話,並且您的實體已經在該會話中(通過另一個操作),則Hibernate實際上可以為您進行臟檢查。 但是,如果您有一個分離的實體,並且該實體在會話中不存在,並說您進行了merge
,則Hibernate首先會通過發出SELECT從數據存儲(數據庫)中獲取該實體,並將該托管實體放入持久性存儲中。上下文,然后它將合並兩個實體,一個是您提供的實體,另一個是休眠的實體,並將其放入持久性上下文中,然后檢查是否有任何更改,從而進行臟檢查。
在您的情況下,由於要從臟檢查中排除最后修改的用戶名和時間,因此最好通過ID獲取實體(因為您可能在分離的實體上擁有ID),然后使用equals()
或hashCode()
來執行您自己的臟檢查版本,如果不是,則調用merge。
您可能會認為這是對數據庫的一次額外旅行,但事實並非如此,因為即使在正常情況下,如果該實體尚未處於持久性上下文(即會話)中並且如果該實體還處於休眠狀態,Hibernate仍會進行對數據庫的額外旅行。是的,並且您執行一個獲取ID,Hibernate只會返回給您會話中已經擁有的內容,而不會訪問數據庫。
此參數不適用於saveOrUpdate,在這種情況下,Hibernate會直接將更新推送到數據庫,而不會進行臟檢查(如果實體不在會話中),並且如果它已在會話中,它將拋出一個說該實體已經在會話中的異常。
萬一任何人需要解決相同的問題而無需更改導致此問題的代碼,我將比較實現為:
/**
* Called upon entity UPDATE. For our BaseEntity, populates updated by with logged in user ID and
* updated date-time.
* <p>
* Note that this method is called even if an object has NOT changed but someone's called a setter on
* a Collection related to a child object (as opposed to modifying the Collection itself). Because of
* that, the comparisons below are necessary to make sure the entity has really changed before setting
* the update date and user.
*
* @see org.hibernate.EmptyInterceptor#onFlushDirty(java.lang.Object, java.io.Serializable, java.lang.Object[], java.lang.Object[], java.lang.String[], org.hibernate.type.Type[])
*/
@Override
public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState,
String[] propertyNames, Type[] types)
{
boolean changed = false;
if (entity instanceof BaseEntity) {
logger.debug("onFlushDirty method called on " + entity.getClass().getCanonicalName());
// Check to see if this entity really changed(see Javadoc above).
boolean reallyChanged = false;
for (int i = 0; i < propertyNames.length; i++) {
// Don't care about the collection types because those can change and we still don't consider
// this object changed when that happens.
if (!(types[i] instanceof CollectionType)) {
boolean equals = Objects.equals(previousState[i], currentState[i]);
if (!equals) {
reallyChanged = true;
break;
}
}
}
if (reallyChanged) {
String userId = somehowGetUserIdForTheUserThatMadeTheRequest();
Date dateTimeStamp = new Date();
// Locate the correct field and update it.
for (int i = 0; i < propertyNames.length; i++) {
if (UPDATE_BY_FIELD_NAME.equals(propertyNames[i])) {
currentState[i] = userId;
changed = true;
}
if (UPDATE_DATE_FIELD_NAME.equals(propertyNames[i])) {
currentState[i] = dateTimeStamp;
changed = true;
}
}
}
}
return changed;
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.