Havoc with Hibernate Filters and Inheritance
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.