I'd like to implement repository method void touch(MyEntity myEntity)
which enforces SQL call of update of entity columns to their current values. (The reason behind is the on update
trigger which needs to be invoked in some point of execution.) Ideal usecase is:
void serviceMethod(Long myEntityId) {
MyEntity myEntity = myEntityRepository.findOne(myEntityId);
...
myEntityRepository.touch(myEntity);
...
}
There are already similar questions on SO which don't work for me: Force update in Hibernate (my entity is detached), Implementing “touch” on JPA entity? (doing some harmless change works but is not general and has bad impact on code readability), Hibernate Idempotent Update (similar example).
I am aware of session interceptor method findDirty
and also CustomEntityDirtinessStrategy
both described in this Vlad Mihalcea's article. However, it seems to use findDirty
I would have to override session interceptor, which is not possible from within repository method since the interceptor is final field assigned to session at session creation. And CustomEntityDirtinessStrategy
comes from SessionFactory
which is global. I rather need some one-shot solution to temporary consider one concrete entity of one concrete class dirty.
The so-far-best working solution is to set invalid (array of nulls) entity snapshot into persistence context, so that the subsequent logic in flush()
evaluates entity as differing from snapshot and enforce update. This works:
@Override
@Transactional
public void touch(final T entity) {
SessionImpl session = (SessionImpl)em.getDelegate();
session.update(entity);
StatefulPersistenceContext pctx = (StatefulPersistenceContext) session.getPersistenceContext();
Serializable id = session.getIdentifier(entity);
EntityPersister persister = session.getEntityPersister(null, entity);
EntityKey entityKey = session.generateEntityKey(id, persister);
int length = persister.getPropertyNames().length;
Field entitySnapshotsByKeyField = FieldUtils.getField(pctx.getClass(), "entitySnapshotsByKey", true);
Map<EntityKey,Object> entitySnapshotsByKey = (Map<EntityKey,Object>)ReflectionUtils.getField(entitySnapshotsByKeyField, pctx);
entitySnapshotsByKey.put(entityKey, new Object[length]);
session.flush();
em.refresh(entity);
}
The advice in Force update in Hibernate didn't work for me because session.evict(entity)
clears entitySnapshotsByKey
entry at all, which causes subsequent org.hibernate.event.internal.DefaultFlushEntityEventListener#getDatabaseSnapshot
loads fresh entity from db. The question is 9 years old and I'm not sure if it's applicable to current version of Hibernate (mine is 5.2.17).
I am not satisfied with such hacky solution though. Is there some straightforward way or something I could do simpler?