Optimal JPA and Hibernate Query Strategies
Optimal JPA and Hibernate Query Strategies
Using JPA and Hibernate, you can choose between various ways to
query your data, and each of them supports one or more kinds of
projections. That provides you with lots of options to implement your
persistence layer. But which one fits your use case? And which one
should you avoid if you want to optimize your persistence layer for
performance?
Entity Projections
For most teams, entity projections are the most commonly used
ones. But that’s often not the best approach. Entities might be easy to
use and are the best fit for all write operations. But as I showed in a
previous article, selecting and managing an entity creates an
overhead that slows down your application.
Select Entities by ID
You could, of course, implement your own query to fetch one or
more entities by their primary key. But you don’t have to. There are
more comfortable options available.
If you’ve ever used JPA or Hibernate, you know the find method of
the EntityManager interface. It provides an easy to use way to load
one entity by its primary key.
[Link]
Optimal Query Approach and Projection
The syntax of a JPQL query is pretty similar to SQL. The query in the
following code snippet selects all Author entities with a given
firstname.
@Entity
@NamedQuery(name = "[Link]", query = "SELECT a
FROM Author a WHERE [Link] = :firstname")
public class Author { ... }
[Link]
Optimal Query Approach and Projection
TypedQuery<Author> q = [Link]("[Link]",
[Link]);
[Link]("firstname", firstname);
List<Author> authors = [Link]();
Here you can see an example that selects all Author entities with a
given firstname. This is the same query as I showed you in the
previous JPQL example. As you can see, the code block that uses the
Criteria API is longer and harder reader.
CriteriaBuilder cb = [Link]();
CriteriaQuery<Author> cq = [Link]([Link]);
Root<Author> root = [Link]([Link]);
The Criteria API supports the same features as JPQL. Explaining all of
them in details would take too long for this article. If you want to
learn more about it, please join my Advanced Hibernate Online
Training.
[Link]
Optimal Query Approach and Projection
Complex and Database-Specific Queries Selecting Entities
If your query gets really complex or if you want to use database-
specific features, you need to use a native SQL query. Hibernate
takes the native query statement and sends it to the database
without parsing it.
If your native query returns all columns mapped by your entity and if
their names are identical to the ones used in the entity mapping, you
just need to provide your entity class as the 2nd parameter to the
createNativeQuery method. Hibernate will then apply the standard
entity mapping to your query result.
The main downside of scalar value projections is that they are very
uncomfortable to use. You can use this projection with a JPQL,
[Link]
Optimal Query Approach and Projection
Criteria, or native SQL query. In all 3 cases, your query returns an
Object[]. When you use this array, you need to remember the position
of each column and cast the array element to the correct type.
Please take a look at the following articles, if you want to use this
projection with a Criteria or native SQL query:
DTO Projections
From a performance point of view, DTO projections are almost as
good as scalar value projections. They provide the best performance
for read operations. But the strong typing and the descriptive
attribute names make this projection so much easier to use.
You can use DTO projections in JPQL, Criteria, and native SQL
queries.
[Link]
Optimal Query Approach and Projection
The definition of a JPQL query that uses a DTO projection is pretty
simple. You define a constructor call in your SELECT clause using the
keyword new followed by the fully qualified name of your DTO class
and a list of parameters.
You define and execute the CriteriaQuery in almost the same way as
the CriteriaQuery I showed you early. The only difference is that you
now need to call the construct method on the CriteriaBuilder to
define the constructor call.
CriteriaBuilder cb = [Link]();
CriteriaQuery<AuthorValue> q = [Link]([Link]);
Root<Author> root = [Link]([Link]);
[Link]([Link]([Link], [Link](Author_.firstName),
[Link](Author_.lastName)));
[Link]
Optimal Query Approach and Projection
Hibernate then executes the native query and iterates through the
result set. For each record, it calls the constructor defined by the
@ConstructorResult annotation.
Here you can see the definition of a constructor call of the BookValue
class. Hibernate will provide the value of the title column as the 1st
and the value of the date column as the 2nd parameter.
@Entity
@SqlResultSetMapping(name = "BookValueMapping",
classes = @ConstructorResult(
targetClass = [Link],
columns = { @ColumnResult(name = "title"),
@ColumnResult(name = "date")}
)
)
public class Book { ... }
Conclusion
When using JPA and Hibernate, you can choose between various ways
to read the required information.
[Link]
Optimal Query Approach and Projection
Java API. And if you need to use the full feature set of your database,
you need to use a native SQL query.
Scalar value projections are not very comfortable to use, and you
should better use a DTO projection.
DTO projections are the best option for read-only operations. They
are strongly typed, easy to use, and provide the best performance.
You can use them with JPQL, Criteria, and native queries.
[Link]