JPQL Considered Harmful
In a Java project, it’s not unusual to find database queries using the Jakarta Persistence Query Language (JPQL), a fairly easy to use language with a syntax akin to SQL. While this is a perfectly acceptable way to declare small queries, it can quickly spiral out of control for more advanced requirements. The Criteria API (and its Spring Data derivative Specifications) allow for a modular, programmatic, and type safe declaration of queries. It’s so good, you’ll probably never want to go back to writing string queries again!
The data access layer is such an ubiquitous component of our applications, one could dare to say it’s the essence of it—business logic is derived from this data after all. As a programmer you’ll spend a great deal of time in this layer, which means you’ve probably run into the following piece of code:
In such a small example you can already spot a few code smells, not to mention that string mangling can become extremely gruesome in most cases. Given the amount of time we spent maintaining modules with this particular pattern found in every method, surely there must be a better way to build complex queries without developer experience trade-offs.
Criteria API
Criteria API was introduced with the release of JSR 317: Java Persistence API, Version 2.0 in 2009. Criteria queries are written in plain Java, are type safe, and just like its JPQL (formerly Java Persistence Query Language) counterpart, they work regardless of the underlying data store.
Switching your plain string queries to Criteria API is, in my opinion, the single most significant improvement you can make to your application’s data layer:
- Drastically enhance the developer experience by unlocking powerful programmatic control flow techniques, without sacrificing readability. Say goodbye to complicated string mangled spaghetti code.
- Reduce potentially hundreds of lines of duplicate queries with little to no differences by encouraging modularity and reuse. Client requests a change be reflected in multiple reports? Modifying a single query is often enough.
- Improve performance by skipping unnecessary
JOIN
clauses you might have to support a single (and rarely materialized) condition in your string appending line ofWHERE
s.
Spring Data JPA abstracts this API even further and introduces
the concept of Specifications as small building blocks which can
be combined and used with JpaRepository
without the need to declare
a query (method) for every needed combination.
Guitar Inventory Example
I’m not joking when I say that after using Criteria API/Specifications you’ll never want to write a single string query again. And to make that case, I’ve created a sample project using the three styles of query definitions. I’m using Spring Boot 2.7 which, while way past its open source support window, still runs Java 8 code. This choice is deliberate so this article can reach a much wider audience.
If you’re not using Spring, but do have access to the EntityManager, then this article still applies to you. So I encourage you to download the source code and follow along with me!
The project consists of a guitar inventory management system for multiple brick-and-mortar store locations:
Business Logic
Suppose the client’s point of sale needs to fetch available guitars for sale based on the following rules:
- By default, only the current store (Roseville) inventory is shown, but one can toggle the full catalog
- Due to a shortage, no maple fretboards are available for sale
- Dave is a new hire and only sells guitars priced at $100 or less
We start by creating a getInventory
method with a signature like so:
Using JPQL (String queries)
Because we wouldn’t want to define a new string query for every
single escenario (which is subject to change), the usual approach is
appending conditionals to the WHERE
clause in the JPQL string:
While the current business logic is not very complex, there’s no doubt that the previous code is unfit for more elaborate requirements. The string mangling will quickly become unmaintainable and the lack of modularity makes it impossible to reuse the conditions in a different query or even expand upon it (i.e. additional filtering). On that last note, we might be tempted to filter the query results in a different layer of our application, like the front-end, but this means losing the performance of the query optimizer of our database to custom filtering code of a data structure (meaning we now have to maintain code in two different locations).
Using Criteria API
With Criteria API, each conditional is represented with a Predicate. These can be manipulated programmatically, expanded upon, and reused across our codebase:
Now you might be wondering just what the heck is this Guitar_
business. Guitar_
(and FretboardWood_
) are metamodel classes. The
metamodel class and its attributes are used in Criteria queries to
refer to the managed entity classes and their persistent state and
relationships. Metamodel classes are typically generated by annotation
processors either at development time or at runtime. A metamodel class
is created with a trailing underscore. Hibernate Metamodel
Generator is a popular annotation processor. The following is the
autogenerated metamodel class for the Guitar
@Entity
:
Thanks to these generated classes, our criteria queries always have the correct attribute names and types. They can easily be updated, should they ever change.
Using Spring Data JPA Specifications
The last approach involves the declaration of specifications. Specification is a functional interface with the following signature:
You typically write these as static methods in a Specs class like so:
Finally, we chain these specs with .or()
& .and()
(as per our
requirements) into a single spec, which we then pass to an
all-too-familiar findAll
method:
Conclusion
JPQL queries are a perfectly valid JPA tool and are not going away any time soon (they’re well supported in Spring Data JPA with the @Query annotation). But a key aspect of being a professional is choosing the right tool for the job, and the benefits of using Criteria API/Specifications for complex querying are far too many to be ignored in favor of string mangling. Criteria API represents an incredibly valuable force multiplier that’s available to you the moment JPQL is.