How to enable bytecode enhancement dirty checking in Hibernate

Introduction

Hibernate runs the automatic dirty checking mechanism during flush-time, and any managed entity state change is translated into an UPDATE SQL statement. The default dirty checking mechanism uses Java reflection and goes through every property of every managed entity.

If the Persistence Context has few entities, this process might go unnoticed, but, if we’re dealing with many entities or the entities have many properties (e.g. a legacy database Domain Model mapping), then the reflection-based dirty checking might have an associated performance impact.

Hibernate offers support for instrumenting entity bytecode for three use cases:

  • lazy initialization (allows entity attributes to be fetched lazily)
  • dirty tracking (the entity tracks its own property changes)
  • association management (allows automatic sides synchronization for bidirectional associations)

Although it featured a bytecode enhancement tool since version 3.x, even in Hibernate 4.x the bytecode enhancement mechanism was not completely implemented for dirty checking.

It’s Hibernate 5 time

Among many other features, Hibernate 5.x comes with a brand new bytecode enhancement implementation which also takes care of the dirty checking mechanism.
Although bytecode enhancement can be done at compile-time, runtime or deploy time, the compile-time alternative is preferred for the following reasons:

  • the enhanced classes can be covered by unit tests
  • the Java EE application server or the stand-alone container (e.g. Spring) can bootstrap faster because there’s no need to instrument classes at runtime or deploy-time
  • class loading issues are avoided since the application server doesn’t have to take care of two versions of the same class (the original and the enhanced one).

To instrument all @Entity classes, you need to add the following Maven plugin:

<plugin>
    <groupId>org.hibernate.orm.tooling</groupId>
    <artifactId>hibernate-enhance-maven-plugin</artifactId>
    <version>${hibernate.version}</version>
    <executions>
        <execution>
            <configuration>
                <enableDirtyTracking>true</enableDirtyTracking>
            </configuration>
            <goals>
                <goal>enhance</goal>
            </goals>
        </execution>
    </executions>
</plugin>

After the Java classes are compiled, the plugin goes through all entity classes and modifies their bytecode according to the instrumentation options chosen during configuration.

When enabling the dirty tracking option, Hibernate will track property changes through the following mechanism.
The $$_hibernate_tracker attribute stores every property change, and every setter method will call the $$_hibernate_trackChange method.

@Transient
private transient DirtyTracker $$_hibernate_tracker;

public void $$_hibernate_trackChange(String paramString) {
    if (this.$$_hibernate_tracker == null) {
        this.$$_hibernate_tracker = new SimpleFieldTracker();
    }
    this.$$_hibernate_tracker.add(paramString);
}

Considering the following original Java entity class setter method:

public void setTitle(String title) {
    this.title = title;
}

Hibernate transforms it to the following bytecode representation:

public void setTitle(String title) {
    if(!EqualsHelper.areEqual(this.title, title)) {
        this.$$_hibernate_trackChange("title");
    }
    this.title = title;
}

When the application developer calls the setTitle method with an argument that differs from the currently stored title,
the change is going to be recorded in the $$_hibernate_tracker class attribute.

During flushing, Hibernate inspects the $$_hibernate_hasDirtyAttributes method to validate if an entity is dirty.
The $$_hibernate_getDirtyAttributes method gives a String[] containing all dirty properties.

public boolean $$_hibernate_hasDirtyAttributes() {
    return $$_hibernate_tracker != null && 
        !$$_hibernate_tracker.isEmpty();
}

public String[] $$_hibernate_getDirtyAttributes() {
    if($$_hibernate_tracker == null) {
        $$_hibernate_tracker = new SimpleFieldTracker();
    }
    return $$_hibernate_tracker.get();
}

If you enjoyed this article, I bet you are going to love my book as well.

Conclusion

Although bytecode enhancement dirty tracking can speed up the Persistence Context flushing mechanism,
if the size of the Persistence Context is rather small, the improvement is not that significant.

The entity snapshot is still saved in the Persistence Context even when using bytecode enhancement.
For this reason, keeping the Persistence Context in reasonable boundaries holds on no matter the dirty tracking mechanism in use.

If you liked this article, you might want to subscribe to my newsletter too.

Advertisements

16 thoughts on “How to enable bytecode enhancement dirty checking in Hibernate

  1. Hi Vlad,

    I thought that one of the main reasons of using enhancement dirty checking is that Hibernate wouldn’t need to take entity snapshots anymore.

    If I’m not wrong an entity snapshot is just used by JPA (and its provider) to verify if there’re any change to a specific entity during dirty checking.

      1. 2nd-level cache, are you sure? If I’m not wrong entity snapshots are only used by EntityManager which is the 1st-level cache.

  2. It seems like this does not work for @MappedSuperclass The super class contains all the setters and there does not seem to be any dirty checking generated for them.

  3. Hi Vlad,

    we have a problem with lazy loaded associations and dirty – checking using bytecode enhancement.

    Basically it boils down to:

    after adding the bytecode enhancement to enable dirty checking, creation of entities triggers a lot of queries that do a size() call on lazy declared associations. These queries are (for me) unexpected for lazy associations (since they’re lazy) and should no be worried about until explicitly queried.

    It looks like https://hibernate.atlassian.net/browse/HHH-10747 and its a major problem in our upgrade path from hibernate 3.

    What do you recommend?

  4. Regarding my last comment,

    I have just solved it by specifying lazy=”extra” on a declaration, which limits the extra queries to one-level deep select count()’s which is acceptable for my use case. So say you load n entities which have 1 lazy=”extra” association, n select count() queries will be done to determine just the current size of the associated entities.(Still do not understand why this is needed for lazy associations though).

    It could still be a major increase in the number of queries when we have a lot of objects to load and/or the number of associations per loaded entity is large, but for now this looks like an acceptable amount of queries for my use-case.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s