JPQL queries
find loads one entity by its id. For everything else — "all posts with more than 50 views", "the number of posts" — there is JPQL (Jakarta Persistence Query Language). It looks like SQL, but works with entities and their fields instead of tables and columns. Hibernate translates a JPQL query into the SQL of your database. Hibernate's own extended version is called HQL; every JPQL query is also valid HQL.
The first query
The examples use the Seed data from the lesson on lazy loading: three posts with 120, 80 and 45 views.
// src/main/java/com/example/blog/Main.java
Seed.run(emf);
emf.runInTransaction(em -> {
List<Post> popular = em.createQuery(
"select p from Post p where p.views > :min order by p.views desc", Post.class)
.setParameter("min", 50)
.getResultList();
popular.forEach(p -> IO.println(p.getTitle() + " (" + p.getViews() + ")"));
});Hibernate: select p1_0.id,p1_0.author_id,p1_0.title,p1_0.views from Post p1_0 where p1_0.views>? order by p1_0.views desc
Hello, Hibernate (120)
Records in Java 25 (80)| Part | Meaning |
|---|---|
select p from Post p | Post is the entity class name, p an alias |
p.views | A field of the entity, not a column |
:min | A named parameter; its value is set with setParameter |
order by p.views desc | Sorting, as in SQL |
The second argument of createQuery is the result type. With it you get a TypedQuery<Post> and a List<Post> without casts.
One value
// src/main/java/com/example/blog/Main.java
Long total = em.createQuery("select count(p) from Post p", Long.class)
.getSingleResult();
IO.println("Total posts: " + total);Hibernate: select count(p1_0.id) from Post p1_0
Total posts: 3count returns Long. getSingleResult expects exactly one row.
A result that may be missing
When the row may not exist, use getSingleResultOrNull, added in Jakarta Persistence 3.2:
// src/main/java/com/example/blog/Main.java
Post found = em.createQuery("select p from Post p where p.title = :title", Post.class)
.setParameter("title", "Lazy loading")
.getSingleResultOrNull();
IO.println("Found: " + found.getTitle());
Post missing = em.createQuery("select p from Post p where p.title = :title", Post.class)
.setParameter("title", "No such post")
.getSingleResultOrNull();
IO.println("Missing: " + missing);Found: Lazy loading
Missing: nullWhy parameters
Never glue values into the query text with +. A parameter is passed to the database separately, so a title like ' or 1=1 -- stays just text and cannot change the query — this protects against SQL injection, the same way PreparedStatement does in JDBC.
Common mistake
JPQL uses entity and field names, not table and column names. If the class Post is mapped to the table posts, the query select p from posts p fails:
java.lang.IllegalArgumentException: org.hibernate.query.sqm.UnknownEntityException: Could not resolve root entity 'posts'The same happens with a typo in a field name:
java.lang.IllegalArgumentException: org.hibernate.query.sqm.UnknownPathException: Could not resolve attribute 'titel' of 'com.example.blog.Post' [select p from Post p where p.titel = 'x']And getSingleResult without a matching row throws jakarta.persistence.NoResultException: No result found for query [...] — that is what getSingleResultOrNull is for.