Hibernate Dirty-Checking with Converted Attributes

A few days ago I ran into an issue in a rather large application that uses Spring & Hibernate.

I found myself debugging an issue where Hibernate tried to update some entities that I had just deleted, the situation was basically like this:

  1. Load entities A, B, C of type Dummy.
  2. Some logic determines that those entities should get deleted.
  3. Ran a Hibernate CriteriaDelete query which basically did “DELETE FROM DUMMY WHERE ID IN (A, B, C);”
  4. The transaction gets committed and suddenly Hibernate tried to run updates on the entities. This obviously failed since the entities no longer existed in the DB.

Typical reason & how to investigate

The updates that Hibernate tried to run would have been obvious to me if we had modified the entities A, B or C after loading them, however that was not the case — at least not in any obvious way.

By debugging into the Hibernate flushing code I ended up in DefaultFlushEntityEventListener.java:

Snippet from DefaultFlushEntityEventListener.java

What I saw in the debugger here was that Hibernate believed that an attribute of these entities was dirty, interestingly it was an attribute that got persisted via a converter, like this:

@Column(columnDefinition = "VARCHAR")
@Convert(converter = TestDtoConverter.class)
public TestDto getTestDto() {

This made me curious how Hibernate does dirty-checking on attributes that have converters. It turned out that Hibernate makes no difference here between objects with converters and ones without: it simply uses the Object’s .equals method.

The anticlimactic truth

This makes sense and is also correct, in the end it turned out that the problem was caused by some code somewhere had reassigned the attribute to a new object. An exact, identical copy, but a new object nonetheless.

Takeaways

I still think this is interesting and something to keep in mind, think about this:

A converter could for example always lowercase a value, so if the value was “asdf” before and we reassign it to “ASDF”, there would be no difference from the database point of view.

However, since Hibernate uses .equals it wouldn’t know that (it doesn’t compare the converters result, but the source object). This makes sense of course, since equals is supposed to be a cheap operation and conversion could be costly. Still, this could be surprising if you didn’t know that.

I’ve created a small test-scenario to play around with this in this github repo/branch.

A related thing to look at would be hibernate custom dirty checking.


Have you run into similar things when using Hibernate with or without converters? Let me know in the comments!