[英]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.