Home > Software Development > JPA and Hibernate: Problems with Polymorphic Queries

JPA and Hibernate: Problems with Polymorphic Queries

JPA supports polymorphism you can handle how entire class hierarchies are persisted.  For instance, let’s your application manages a bunch of goods.  Nevertheless, particular varieties of goods need extra fields.  So, we can treat these as subclasses of Product.  JPA will permit us to search all varieties of goods at as soon as or restrict their search to particular subtypes.

Unfortunately, Hibernate’s support for restricting, grouping, and sorting by subtype is broken.  At the time of writing, there are several open bugs with patches that have not been addressed.  Until one of the wonderful volunteers on this great project gets a likelihood to deal with this difficulty, Hibernate is left with limited assistance for polymorphism in JPQL and no assistance in the Criteria API.

Fortunately, with very small effort this limitation can be worked about.

An Overview of the Problem

Consider a JPA managed entity called Product.  We have two subclasses: RetailProduct and WholesaleProduct.  JPA will allow us to decide how these products are stored (i.e. in one table or in separate tables) and to search for products by type.  For example, we can search for all products of any type:

from Product where …

Or limit our search to retail products:

from RetailProduct where …

So far, all of this works perfectly in Hibernate.

However, if you want to group by or order by type you should be able to execute the following statement:

from Product p order by type(p)

In fact, you should be able to use the type function anywhere in your statement.  Unfortunately, Hibernate does not support parameterized type functions, which dramatically limits their use with JPQL, and has absolutely no support for the type function in the Criteria API.

A Workaround

Unfortunately, JPA does not permit access to the discriminator — the column that identifies the particular subtype of entity for each row.  However, with a little effort the discriminator can be exposed to the query API.

Consider the Product class:

@DiscriminatorColumn(name="type", length=50)
public class Product {

We can add an additional property (column) to the Product class that matches the discriminator in terms of name and type.  This property must be read only (insertable and updatable must be false) and I would highly recommend it be made private so that it cannot inadvertently be changed in code.

Here’s an example using fields to map columns (as opposed to properties):

@DiscriminatorColumn(name="type", length=50)
public class Product {
	@Column(insertable=false, updatable=false)
	private String type;

Since the field is neither insertable nor updatable Hibernate will allow it.  Since the field is private it cannot be accessed, much less changed, by client code.

Now, this property can be accessed in JPQL queries:

from Product p order by p.type

And through the Criteria API:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Product> query = cb.createQuery(Product.class);
Root<Product> root = query.from(Product.class);
query.where(cb.equal(root.get(AbstractProduct_.type), RetailProduct.class.getSimpleName()));

It is very important identify a couple of issues with using this approach:

  • The property “type” evaluates to what is actually in the discriminator column: in this case, a String, not a Class.  Its value must be quoted in a query and must be identical to how the discriminator is represented in the database (sometimes the class’s simple name; but can be anything: like a code or number).
  • This approach does not support polymorphism.  For instance, if there were subclasses of RetailProduct, they would not be return as part of the above query.  Only records with RetailProduct in the discriminator column would be returned.  You can use p.type in (?, ?, ?) to simulate polymorphism, however.  You would need to list all of the possible types.  In a situation like this, however, you are probably better off changing the type in the from clause.
  • I do not know how portable this is.  Other JPA providers may throw a fit when they see a column definition with the same name as the discriminator.

But, if you are stuck with Hibernate it will work.

Categories: Software Development Tags: ,
  1. No comments yet.