5

I have been trying to figure out how to fix this issue from couple of days. It is bad that hibernate did not provide ready to use annotation to exclude a property from dirty checking. Here is the problem, I have a class as below

Class A {
@Column 
Property 1
@Column 
Property 2..etc

@Column
Date lastUpdateDate
@Column
String lastUpdateBy
}

I want to exclude lastUpdateDate and lastUpdateBy from dirty check! Here is what I am doing

  1. using Dynamic-Update = true

  2. Using interceptor and overriding findDirty method to tell hibernate if the object is dirty (here, previousState is always null because the object is transient so no way to compare old values)

  3. merge would fix the issue but it is causing performance problems (few million records ) so I have to use saveOrUpdate which does not pass previousState to Interceptor

Is there any other way to fix this issue?

pvr
  • 51
  • 1
  • 3

4 Answers4

3

You can use @OptimisticLock(excluded = true) to exclude it from dirty checking.

DOCS

A0__oN
  • 8,740
  • 6
  • 40
  • 61
3

It is possible to ignore specific entity fields from dirty check. You must override DefaultFlushEntityEventListener like this:

@Component
public class CustomFlushEntityEventListener extends DefaultFlushEntityEventListener {

    private static final List<String> IGNORE_DIRTY_CHECK_PROPERTIES = List.of(
        "lastModifiedBy",
        "lastModifiedDate"
    );

    @Override
    protected void dirtyCheck(final FlushEntityEvent event) throws HibernateException {
        super.dirtyCheck(event);
        removeIgnoredDirtyCheckProperties(event);
    }

    private void removeIgnoredDirtyCheckProperties(final FlushEntityEvent event) {
        var propertyNames = event.getEntityEntry().getPersister().getPropertyNames();
        var dirtyProperties = event.getDirtyProperties();
        if(dirtyProperties == null) return;

        var newDirtyProperties = new java.util.ArrayList<Integer>();

        for (int dirtyProperty : dirtyProperties) {
            if (!IGNORE_DIRTY_CHECK_PROPERTIES.contains(propertyNames[dirtyProperty])) {
                newDirtyProperties.add(dirtyProperty);
            }
        }

        var newDirtyPropertiesArray = newDirtyProperties.stream().mapToInt(i -> i).toArray();
        event.setDirtyProperties(newDirtyPropertiesArray.length > 0 ? newDirtyPropertiesArray : null);
    }

}

and then replace listener:

@Component
@RequiredArgsConstructor
public class HibernateListenerConfigurer {

    private final EntityManagerFactory entityManagerFactory;
    private final CustomFlushEntityEventListener customFlushEntityEventListener;

    @PostConstruct
    protected void init() {
        SessionFactoryImpl sessionFactory = entityManagerFactory.unwrap(SessionFactoryImpl.class);
        EventListenerRegistry registry = sessionFactory.getServiceRegistry().getService(EventListenerRegistry.class);
        registry.getEventListenerGroup(EventType.FLUSH_ENTITY).clear();
        registry.getEventListenerGroup(EventType.FLUSH_ENTITY).appendListener(customFlushEntityEventListener);
    }

}

You can also replace IGNORE_DIRTY_CHECK_PROPERTIES list with custom annotation on entity property for example @IgnoreDirtyCheck and read it from event.getEntity() by reflection.

zolv
  • 1,720
  • 2
  • 19
  • 36
Paweł Dec
  • 31
  • 2
0

You should probably use hibernate / JPA entity listener to update your properties (lastUpdateDate)

https://docs.jboss.org/hibernate/entitymanager/3.5/reference/en/html/listeners.html

it's a standard solution easier to use than implementing your own dirty checking.

benbenw
  • 723
  • 7
  • 20
-1

Try adding updateable=false to your @Column. It causes that field to not be included in the update statements so I would think it would exclude it from dirty checking.

Check out this link:

@javax.persistence.Column( updatable=false )

Community
  • 1
  • 1
Josh Chappelle
  • 1,558
  • 2
  • 15
  • 37
  • 1
    Thanks for the reply, the purpose of lastUpdated column is to update db with timestamp whenever the object changed. I dont want to update lastUpdated column only when none of the other fields have changed :) – pvr Nov 26 '15 at 03:42
  • We use a lastUpdated column quite a bit and just treat it as another column. Why do you need to remove it from dirty checking? – Josh Chappelle Nov 26 '15 at 03:45
  • 1
    If there are no other changes to the row, I dont want to update lastUpdate alone. There are number of other data columns which matter lastUpdate is just an audit column so if the actual data does not change updating lastUpdate would be misleading besides that we have triggers on the table which will fire for every update – pvr Nov 26 '15 at 03:50
  • 1
    It sounds like to me you are doing it the right way. Or at least the way I would approach that problem. You are using the mechanism that the framework developers created for this specific use case. You've probably seen this article but here it is anyway. http://vladmihalcea.com/2014/08/29/how-to-customize-hibernate-dirty-checking-mechanism/ – Josh Chappelle Nov 26 '15 at 03:58
  • 1
    If you read that article and create a SelfDirtyCheckingEntity like they did, then you could create your own annotation called @IgnoreFromDirtyCheck and honor it by using the reflection api to see if that property has that annotation. – Josh Chappelle Nov 26 '15 at 04:01