How to enable bytecode enhancement dirty checking in Hibernate

Imagine having a tool that can automatically detect if you are using JPA and Hibernate properly. Hypersistence Optimizer is that tool!

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();
}

Online Workshops

If you enjoyed this article, I bet you are going to love my upcoming Online Workshops!

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.

Transactions and Concurrency Control eBook

4 Comments on “How to enable bytecode enhancement dirty checking in Hibernate

  1. Hi, I was trying to introduce the bytecode enhancement into one of our projects, but the tests explode for multiple reasons, and I’m not sure how to work around that.
    1) (solved) concurrentModificationException in oneToManyMappings (due to this implementation when setting properties): solution: iterate someList and add backReferences, then assign to this.someList
    …following version exploded with instrumentation:
    this.someList = someList
    if(this.someList != null)
    { this.someList.forEach(el -> el.setBackReference(this)); } // names changed
    2) Several hacky mappings where the other side of the bidirectional relation only has the id not the whole entity (since we don’t need more that the fk on the other side): propably solved (changed to mapping the entity itself -> was some error about a setter expecting the wrong type -> id type vs entity type), also a problem for hibernate versions > 5.2.13 (https://hibernate.atlassian.net/browse/HHH-12436). Solved by workarounds.
    3) A test where:
    *) we fetch a childEntity from a parent via getOne (the repository reposible for the child), just to verify its existence
    *) repo.deleteAllByParentId(123L)
    *) em.flush() // to make sure that the deletion query is flushed before trying to fetch the entity again
    *) Assertions.assertThat(repo.findById(123L)).isEmpty();

    This test works fine with @Transactional on the test itself, when bytecode enhancement is disabled, but with the bytecode enhancement, according to the jpa logs (trace), the delete statement still is executed after the second repo.findById(123L) instead of before (verified that it is executed before without the bytecode enhancement).
    The Interesting thing is though: when the test is not transactional and only the deletion query is transactional, it works fine. It’s like the em.flush() is ignored when I add bytecode enhancement
    I can work around that maybe in some instances but what is the point of em.flush() if it is ignored with bytecode enhancement (and I need that in some tests).

    4) (Don’t know how to solve that easily): Assertj complains about missing properties in entities (of course, using reflection and picking up the added properties unfortunately).
    With enough time, yeah I could write projections or additional domain objects that I map to before verifying the properties with assertj but that is too much work.

    A writeup of all pitfalls that come up with adding bytecode enhancement would be really nice.

    Unfortunately as mentioned above we have a few hacky mappings (that I believe do not really conform to JPA but kinda work with hibernate ..at least with all versions below 5.2.14).
    We have a few entities that are mapped via mapsId (on the child-side), where the child property on the parent is annotated with PrimaryKeyJoinColumn (which I believe is wrong, I’m not sure what effect that has.. this was document in very few places I think, but as far as I can tell, this annotation is strictly used for single table inheritance). At best, this annotation does nothing here, and at worst it does something that kinda works with hibernate but is not conforming to JPA.

    Thanks for any insights.

    • It’s hard to tell what the problem might be without thoroughly analyzing your code base. Therefore, if you are interested in my consulting services, let me know, and we could arrange a one or two days session in January to help your company deal with these problems.

      • I will think about writing up something. TLDR: I have no authority to speak for my company (just a developer there that tries to come up with useful improvements), and we don’t really have the time or budget for that, thanks anyway for the consulting suggestion.
        Maybe I will write-up some concrete test samples.
        I guess there is no hope to fix this in a short time. Too much stuff involved. Next time I hope we will use the byte code instrumentation right from the start.

      • I never had to use bytecode enhancement in a large project. Usually, you can avoid it via all sorts of strategies. Check out my High-Performance Java Persistence book for more details.

Leave a Reply

Your email address will not be published. Required fields are marked *

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