skip to content

What is JPQL, and how does a JPQL query differ from the SQL statement that eventually runs against the database?

level: juniorimportance: must knowfreq 78%

answer

  1. Entity names + field names, not tables/columns
  2. Provider translates to dialect SQL
  3. select b returns managed entities
  4. No LIMIT — setMaxResults instead
  5. HQL = JPQL superset

basics

~20 s

JPQL is a query language over the entity model: you name entity classes and their Java field names, not tables and columns. The persistence provider translates it into vendor-specific SQL using the mappings, and returns managed entities rather than rows.

solid answer

~50 s

JPQL (Jakarta Persistence Query Language) queries the **object model**. `select b from Book b where b.title like :t` refers to the entity name `Book` and the field `title`; the provider consults the mappings to produce SQL against whatever table and column those are mapped to, in the dialect of the current database. Key differences from SQL: - **Identifiers**: entity and attribute names, case-sensitive as Java is; keywords are case-insensitive. - **Results**: `select b` returns managed `Book` entities attached to the persistence context, not rows. Selecting individual attributes returns scalars or `Object[]` tuples. - **Joins** follow mapped associations rather than explicit key columns. - **Portability**: the same JPQL runs on any supported database because the dialect handles vendor differences. - **Scope**: JPQL only sees what is mapped — no arbitrary tables, no vendor DDL, and only the operations the spec defines. HQL is Hibernate's superset of JPQL, adding features beyond the specification.

code

java · 9 lines
java
List<Book> entities = em.createQuery(
        "select b from Book b where b.price > :min", Book.class)
    .setParameter("min", new BigDecimal("30"))
    .getResultList();

List<Object[]> scalars = em.createQuery(
        "select b.title, b.price from Book b where b.price > :min", Object[].class)
    .setParameter("min", new BigDecimal("30"))
    .getResultList();

go deeper

for a junior

State that JPQL targets entities and fields, that the provider generates SQL, and give one small correct example.

for a middle

Add managed-entity results, dialect-driven portability, paging via setMaxResults, and the JPQL/HQL relationship.

for a senior

Discuss what the abstraction hides — generated SQL, join counts, flush-before-query — and when native SQL is the better tool.

for a principal

Frame JPQL as a portability and model-alignment boundary: what it buys across databases, what it costs in SQL control, and where a team should deliberately drop to native queries.

## The idea JPQL is the query language defined by the Jakarta Persistence specification (formerly JPA). It looks like SQL on purpose — `SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY` — but it operates one level up: over **entities, their attributes and their associations** rather than tables, columns and foreign keys. Hibernate's own language, HQL, is a strict superset: every JPQL query is valid HQL, and HQL adds more. ## What you actually write ```java List<Book> books = em.createQuery( "select b from Book b where b.author.name = :name order by b.title", Book.class) .setParameter("name", "Le Guin") .getResultList(); ``` - `Book` is the **entity name** — by default the simple class name, overridable with `@Entity(name = "...")`. It is *not* the table name; if the class maps to `TBL_BOOKS`, the JPQL still says `Book`. - `b` is an alias (the spec calls it an identification variable). It is effectively required, and every path starts from it. - `b.author.name` is a path expression across a mapped association. - `:name` is a named parameter; values are bound, never concatenated. - The provider generates dialect-specific SQL and maps result rows back to `Book` instances. ## Entity results are managed The most consequential difference from SQL: `select b from Book b` returns **managed entities**. They are attached to the persistence context, changes to them are tracked, and repeated identifiers within one context resolve to the same object instance. A JDBC result set has no such notion. This is also why a query can return an entity you already modified in memory — the provider flushes pending changes before running a query whose result could be affected, so the query sees your work. You can also select non-entity values: `select b.title, b.price from Book b` yields `Object[]` per row (`Tuple` if you ask for it), and `select count(b) from Book b` yields a `Long`. Those results are plain values, not managed. ## Syntax rules worth memorising - Keywords (`select`, `from`, `where`) are case-insensitive; entity and attribute names are case-sensitive. - `select b` may be omitted in HQL (`from Book b`), but the spec expects an explicit select clause. - Comparison, arithmetic, `BETWEEN`, `IN`, `LIKE`, `IS NULL`, `AND/OR/NOT` all behave as you expect. - Collection-specific operators exist that SQL has no equivalent for: `IS EMPTY`, `MEMBER OF`, `SIZE(b.tags)`. - Aggregates (`count`, `sum`, `avg`, `min`, `max`), `group by`, `having` and subqueries are all supported. - There is no `LIMIT` in JPQL — paging is done through `setFirstResult`/`setMaxResults`, and the provider emits the vendor's own limit syntax. (HQL does add a `limit`/`offset` clause.) ## What JPQL cannot do It sees only the mapped model. Unmapped tables, database-specific DDL, hierarchical queries, and most vendor extensions are out of reach — that is what native queries are for. It also has a fixed function set; anything else needs the `FUNCTION(...)` escape or HQL. ## Why use it at all Three reasons. **Portability**: one query text runs on PostgreSQL, MySQL or H2, with the dialect absorbing differences in paging, string functions and identifier quoting. **Model alignment**: renaming a column is a mapping change, not a rewrite of every query, and refactoring tools can see attribute names. **Integration**: results are managed entities, participating in dirty checking, cascading and the caches; the second-level query cache and statistics also key off JPQL. The cost is a layer of indirection: the SQL you get is not the SQL you wrote, so reading the generated statements is part of working with JPQL rather than an optional debugging step. ## JPQL versus HQL in one line JPQL is the portable specification subset; HQL is Hibernate's dialect of it, adding features such as an explicit limit clause, set operations, window functions, subqueries in FROM and direct calls to registered database functions. Sticking to JPQL keeps you provider-portable; reaching into HQL buys expressiveness at the cost of that portability, which in most projects is a fine trade — provided the team makes it knowingly.

  • If an entity class Book is annotated @Table(name = "TBL_BOOKS"), what name do you use in a JPQL query?
    You use the entity name, which defaults to the simple class name Book, or whatever @Entity(name = ...) declares. The table name is a mapping detail the provider applies when generating SQL; putting TBL_BOOKS in JPQL raises a parse error about an unknown entity.
  • How do you limit the number of rows a JPQL query returns, given there is no LIMIT keyword?
    Call setFirstResult and setMaxResults on the Query or TypedQuery; the provider renders the database's own paging syntax, whether that is LIMIT/OFFSET, FETCH FIRST or a rownum wrapper. Hibernate's HQL additionally supports an explicit limit/offset clause in the query text, but that is outside portable JPQL.

JPQL is querying the floor plan of your object model; SQL is querying the bricks. The mapping is the architect's key that translates one into the other.

saying these in an interview costs you the question

  • Using table and column names in JPQL instead of entity and attribute names
  • Believing JPQL is just a thin string wrapper that passes SQL through
  • Writing LIMIT/TOP in JPQL
  • Assuming any database function can be called directly in portable JPQL
  • Thinking JPQL results are plain data — missing that selected entities are managed

context