Havoc with Hibernate Filters and Inheritance

Sunday, Jun 10 2018 in java

After migrating from Hibernate 3.6 to Hibernate 5.2, our web framework would sometimes fail mysteriously to bind controller method arguments to the corresponding persistent entity. The cause turned out to be an obscure interaction between JPA’s single table inheritance and a misconfigured Hibernate filter.

Usually, our web framework would interpret the request parameters, create an instance of the correct class, and add it to the persistence context. If an object with that ID already existed in the database, it would retrieve it and merge the existing fields with the request parameters in a smart way. After upgrading Hibernate, the process would fail for instances of a specific class, let’s call it Item.

Same query, different results

While investigating the issue, we found out that

em.createQuery("Select i where Item i where i.id = :id", Item.class)
  .setParameter("id", id)
  .getSingleResult();

would always throw NoResultException, while

em.find(Item.class, id)

would retrieve the entity. This does not make sense: both calls should generate identical queries!

It then occurred to us that the issue might be related to filters, since filters don’t apply to direct fetching with find().

A short investigation turned up that we had annotated the Item class with a filter base on a field it did not have!

@Entity
@Filter(name = "widgetonly")
public class Item {
}

Could that be the cause of the failing queries?

Inheritance Struggles

It turns Both Item and another class, Widget, extend AbstractSuperClass. We had mapped the inheritance structure with the single table inheritance strategy:

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
abstract public class AbstractSuperClass {
}

Only Widget defines the widgetOnly field, but because Widget and Item share the same table in the database, rows representing an Item instance have a widgetOnly column too, but its value is always NULL.

The @Filter annotation confused Hibernate: Hibernate 5.2 started filtering out the rows where widgetOnly is NULL, even though widgetOnly was not a field on Item. After removing the @Filter annotation, all queries returned the correct Item instance.

Undefined behaviour bites you

Upgrading Hibernate caused this bug because we relied on implicitly undefined behaviour: the Hibernate documentation does not state what happens if you define a @Filter on a missing field. But this bug also highlights two anti-patterns.

The first anti-pattern concerns the single table strategy: it creates a divergence between the database and the Java view of the world: for the Java code there are multiple entities, but the database sees one table.

The second anti-pattern concerns the filters: it should be possible to check early when building the entity graph that @Filter annotations refer to existing fields.