VOOZH about

URL: https://www.javacodegeeks.com/2014/09/how-to-customize-hibernate-dirty-checking-mechanism.html

⇱ How to customize Hibernate dirty checking mechanism


Introduction

In my previous article I described the Hibernate automatic dirty checking mechanism. While you should always prefer it, there might be times when you want to add your own custom dirtiness detection strategy.

Custom dirty checking strategies

Hibernate offers the following customization mechanisms:
 

A manual dirty checking exercise

As an exercise, I’ll build a manual dirty checking mechanism to illustrate how easy you can customize the change detection strategy:

Self dirty checking entity

First, I’ll define a DirtyAware interface all manual dirty checking entities will have to implement:

public interface DirtyAware {

 Set<String> getDirtyProperties();

 void clearDirtyProperties();
}

Next I am going to encapsulate our current dirty checking logic in a base class:

public abstract class SelfDirtyCheckingEntity implements DirtyAware {

 private final Map<String, String> setterToPropertyMap = new HashMap<String, String>();

 @Transient
 private Set<String> dirtyProperties = new LinkedHashSet<String>();

 public SelfDirtyCheckingEntity() {
 try {
 BeanInfo beanInfo = Introspector.getBeanInfo(getClass());
 PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
 for (PropertyDescriptor descriptor : descriptors) {
 Method setter = descriptor.getWriteMethod();
 if (setter != null) {
 setterToPropertyMap.put(setter.getName(), descriptor.getName());
 }
 }
 } catch (IntrospectionException e) {
 throw new IllegalStateException(e);
 }

 }

 @Override
 public Set<String> getDirtyProperties() {
 return dirtyProperties;
 }

 @Override
 public void clearDirtyProperties() {
 dirtyProperties.clear();
 }

 protected void markDirtyProperty() {
 String methodName = Thread.currentThread().getStackTrace()[2].getMethodName();
 dirtyProperties.add(setterToPropertyMap.get(methodName));
 }
}

All manual dirty checking entities will have to extend this base class and explicitly flag the dirty properties through a call to the markDirtyProperty method.

The actual self dirty checking entity looks like this:

@Entity
@Table(name = "ORDER_LINE")
public class OrderLine extends SelfDirtyCheckingEntity {

 @Id
 @GeneratedValue(strategy = GenerationType.AUTO)
 private Long id;

 private Long number;

 private String orderedBy;

 private Date orderedOn;

 public Long getId() {
 return id;
 }

 public Long getNumber() {
 return number;
 }

 public void setNumber(Long number) {
 this.number = number;
 markDirtyProperty();
 }

 public String getOrderedBy() {
 return orderedBy;
 }

 public void setOrderedBy(String orderedBy) {
 this.orderedBy = orderedBy;
 markDirtyProperty();
 }

 public Date getOrderedOn() {
 return orderedOn;
 }

 public void setOrderedOn(Date orderedOn) {
 this.orderedOn = orderedOn;
 markDirtyProperty();
 }
}

Whenever a setter gets called, the associated property becomes dirty. For simplicity sake this simple exercise doesn’t cover the use case when we revert a property to its original value.

The dirty checking test

To test the self dirty checking mechanisms I’m going to run the following test case:

@Test
public void testDirtyChecking() {
 doInTransaction(new TransactionCallable<Void>() {
 @Override
 public Void execute(Session session) {
 OrderLine orderLine = new OrderLine();
 session.persist(orderLine);
 session.flush();
 orderLine.setNumber(123L);
 orderLine.setOrderedBy("Vlad");
 orderLine.setOrderedOn(new Date());
 session.flush();
 orderLine.setOrderedBy("Alex");
 return null;
 }
 });
}

The Hibernate Interceptor solution

The Hibernate Interceptor findDirty callback allows us to control the dirty properties discovery process. This method may return:

  • null, to delegate the dirty checking to Hibernate default strategy
  • an int[] array, containing the modified properties indicies

Our Hibernate dirty checking interceptor looks like this:

public class DirtyCheckingInterceptor extends EmptyInterceptor {
 @Override
 public int[] findDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) {
 if(entity instanceof DirtyAware) {
 DirtyAware dirtyAware = (DirtyAware) entity;
 Set<String> dirtyProperties = dirtyAware.getDirtyProperties();
 int[] dirtyPropertiesIndices = new int[dirtyProperties.size()];
 List<String> propertyNamesList = Arrays.asList(propertyNames);
 int i = 0;
 for(String dirtyProperty : dirtyProperties) {
 LOGGER.info("The {} property is dirty", dirtyProperty);
 dirtyPropertiesIndices[i++] = propertyNamesList.indexOf(dirtyProperty);
 }
 dirtyAware.clearDirtyProperties();
 return dirtyPropertiesIndices;
 }
 return super.findDirty(entity, id, currentState, previousState, propertyNames, types);
 }
 }

When passing this interceptor to our current SessionFactory configuration we get the following output:

INFO [main]: c.v.h.m.l.f.InterceptorDirtyCheckingTest - The number property is dirty
INFO [main]: c.v.h.m.l.f.InterceptorDirtyCheckingTest - The orderedBy property is dirty
INFO [main]: c.v.h.m.l.f.InterceptorDirtyCheckingTest - The orderedOn property is dirty
DEBUG [main]: o.h.e.i.AbstractFlushingEventListener - Flushed: 0 insertions, 1 updates, 0 deletions to 1 objects
DEBUG [main]: n.t.d.l.SLF4JQueryLoggingListener - Name: Time:1 Num:1 Query:{[update ORDER_LINE set number=?, orderedBy=?, orderedOn=? where id=?][123,Vlad,2014-08-20 07:35:05.649,1]} 
INFO [main]: c.v.h.m.l.f.InterceptorDirtyCheckingTest - The orderedBy property is dirty
DEBUG [main]: o.h.e.i.AbstractFlushingEventListener - Flushed: 0 insertions, 1 updates, 0 deletions to 1 objects
DEBUG [main]: n.t.d.l.SLF4JQueryLoggingListener - Name: Time:0 Num:1 Query:{[update ORDER_LINE set number=?, orderedBy=?, orderedOn=? where id=?][123,Alex,2014-08-20 07:35:05.649,1]}

The manual dirty checking mechanism has detected incoming changes and propagated them to the flushing event listener.

The lesser-known CustomEntityDirtinessStrategy

The CustomEntityDirtinessStrategy is a recent Hibernate API addition, allowing us to provide an application specific dirty checking mechanism. This interface can be implemented as follows:

public static class EntityDirtinessStrategy implements CustomEntityDirtinessStrategy {

 @Override
 public boolean canDirtyCheck(Object entity, EntityPersister persister, Session session) {
 return entity instanceof DirtyAware;
 }

 @Override
 public boolean isDirty(Object entity, EntityPersister persister, Session session) {
 return !cast(entity).getDirtyProperties().isEmpty();
 }

 @Override
 public void resetDirty(Object entity, EntityPersister persister, Session session) {
 cast(entity).clearDirtyProperties();
 }

 @Override
 public void findDirty(Object entity, EntityPersister persister, Session session, DirtyCheckContext dirtyCheckContext) {
 final DirtyAware dirtyAware = cast(entity);
 dirtyCheckContext.doDirtyChecking(
 new AttributeChecker() {
 @Override
 public boolean isDirty(AttributeInformation attributeInformation) {
 String propertyName = attributeInformation.getName();
 boolean dirty = dirtyAware.getDirtyProperties().contains( propertyName );
 if (dirty) {
 LOGGER.info("The {} property is dirty", propertyName);
 }
 return dirty;
 }
 }
 );
 }

 private DirtyAware cast(Object entity) {
 return DirtyAware.class.cast(entity);
 }
 }

To register the CustomEntityDirtinessStrategy implementation we have to set the following Hibernate property:

properties.setProperty("hibernate.entity_dirtiness_strategy", EntityDirtinessStrategy.class.getName());

Running our test yields the following output:

INFO [main]: c.v.h.m.l.f.CustomEntityDirtinessStrategyTest - The number property is dirty
INFO [main]: c.v.h.m.l.f.CustomEntityDirtinessStrategyTest - The orderedBy property is dirty
INFO [main]: c.v.h.m.l.f.CustomEntityDirtinessStrategyTest - The orderedOn property is dirty
DEBUG [main]: o.h.e.i.AbstractFlushingEventListener - Flushed: 0 insertions, 1 updates, 0 deletions to 1 objects
DEBUG [main]: n.t.d.l.SLF4JQueryLoggingListener - Name: Time:1 Num:1 Query:{[update ORDER_LINE set number=?, orderedBy=?, orderedOn=? where id=?][123,Vlad,2014-08-20 12:51:30.068,1]} 
INFO [main]: c.v.h.m.l.f.CustomEntityDirtinessStrategyTest - The orderedBy property is dirty
DEBUG [main]: o.h.e.i.AbstractFlushingEventListener - Flushed: 0 insertions, 1 updates, 0 deletions to 1 objects
DEBUG [main]: n.t.d.l.SLF4JQueryLoggingListener - Name: Time:0 Num:1 Query:{[update ORDER_LINE set number=?, orderedBy=?, orderedOn=? where id=?][123,Alex,2014-08-20 12:51:30.068,1]}

Conclusion

Although the default field-level checking or the bytecode instrumentation alternative are sufficient for most applications, there might be times when you want to gain control over the change detection process. On a long-term project, it’s not uncommon to customize certain built-in mechanisms, to satisfy exceptional quality of service requirements. A framework adoption decision should also consider the framework extensibility and customization support.

Do you want to know how to develop your skillset to become a Java Rockstar?
Subscribe to our newsletter to start Rocking right now!
To get you started we give you our best selling eBooks for FREE!
1. JPA Mini Book
2. JVM Troubleshooting Guide
3. JUnit Tutorial for Unit Testing
4. Java Annotations Tutorial
5. Java Interview Questions
6. Spring Interview Questions
7. Android UI Design
and many more ....
I agree to the Terms and Privacy Policy

Thank you!

We will contact you soon.

👁 Photo of Vlad Mihalcea
Vlad Mihalcea
September 5th, 2014Last Updated: September 5th, 2014
0 315 4 minutes read

Vlad Mihalcea

Vlad Mihalcea is a software architect passionate about software integration, high scalability and concurrency challenges.
Subscribe

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button
Close
wpDiscuz