0

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?

Tomáš Záluský
  • 10,735
  • 2
  • 36
  • 64
  • Why don't you use a native SQL update? – Simon Martinelli Oct 14 '19 at 09:13
  • @SimonMartinelli probably because I was too focused to make solution in "Force update..." question working :-). Native SQL is actually nice idea. Just thinking how complex would be to synthetize `update my_entity set id = id where id = 1` command from Hibernate metadata, especially for entities with composite key etc. – Tomáš Záluský Oct 14 '19 at 09:44

0 Answers0