Hubbry Logo
Jakarta Persistence Query LanguageJakarta Persistence Query LanguageMain
Open search
Jakarta Persistence Query Language
Community hub
Jakarta Persistence Query Language
logo
7 pages, 0 posts
0 subscribers
Be the first to start a discussion here.
Be the first to start a discussion here.
Jakarta Persistence Query Language
Jakarta Persistence Query Language
from Wikipedia
Jakarta Persistence Query Language
OSCross-platform
Websiteeclipse-ee4j.github.io/jakartaee-tutorial/#the-jakarta-persistence-query-language
Influenced by
SQL, Hibernate

The Jakarta Persistence Query Language (JPQL; formerly Java Persistence Query Language) is a platform-independent object-oriented query language[1]: 284, §12  defined as part of the Jakarta Persistence (JPA; formerly Java Persistence API) specification.

JPQL is used to make queries against entities stored in a relational database. It is heavily inspired by SQL, and its queries resemble SQL queries in syntax,[1]: 17, §1.3  but operate against JPA entity objects rather than directly with database tables.[1]: 26, §2.2.3 

In addition to retrieving objects (SELECT queries), JPQL supports set based UPDATE and DELETE queries.

Examples

[edit]

Example JPA Classes, getters and setters omitted for simplicity.

@Entity
public class Author {
    @Id
    private Integer id;
    private String firstName;
    private String lastName;
 
    @ManyToMany
    private List<Book> books;
}
 
@Entity
public class Book {
    @Id
    private Integer id;
    private String title;
    private String isbn;
 
    @ManyToOne
    private Publisher publisher;
 
    @ManyToMany
    private List<Author> authors;
}
 
@Entity
public class Publisher {
    @Id
    private Integer id;
    private String name;
    private String address;
 
    @OneToMany(mappedBy = "publisher")
    private List<Book> books;
}

Then a simple query to retrieve the list of all authors, ordered alphabetically, would be:

SELECT a FROM Author a ORDER BY a.firstName, a.lastName

To retrieve the list of authors that have ever been published by XYZ Press:

SELECT DISTINCT a FROM Author a INNER JOIN a.books b WHERE b.publisher.name = 'XYZ Press'

JPQL supports named parameters, which begin with the colon (:). We could write a function returning a list of authors with the given last name as follows:

import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;

...

public List<Author> getAuthorsByLastName(String lastName) {
    String queryString = "SELECT a FROM Author a " +
                         "WHERE a.lastName IS NULL OR LOWER(a.lastName) = LOWER(:lastName)";

    TypedQuery<Author> query = getEntityManager().createQuery(queryString, Author.class);
    query.setParameter("lastName", lastName);
    return query.getResultList();
}

Hibernate Query Language

[edit]

JPQL is based on the Hibernate Query Language (HQL), an earlier non-standard query language included in the Hibernate object-relational mapping library.

Hibernate and the HQL were created before the JPA specification. As of Hibernate 3 JPQL is a subset of HQL.

Citations

[edit]

References

[edit]

See also

[edit]
[edit]
Revisions and contributorsEdit on WikipediaRead on Wikipedia
from Grokipedia
The Jakarta Persistence Query Language (JPQL) is a platform-independent, object-oriented defined as part of the specification for writing portable queries against entities and their persistent state in Java applications. It employs an SQL-like syntax to select, update, or delete objects based on entity types, attributes, and relationships, while abstracting the underlying schema to ensure compatibility across different data stores. JPQL operates within the persistence context managed by an EntityManager, using the abstract persistence schema of entities—including single-valued and collection-based relationships—as its data model. Queries are scoped to entities in the same persistence unit and support dynamic creation via methods like createQuery(String qlString) or static named queries, with parameters handled positionally (e.g., ?1) or by name (e.g., :param). Standard clauses include SELECT, FROM, WHERE, and optional elements such as GROUP BY, HAVING, ORDER BY, and path expressions for navigating entity relationships (e.g., e.address.street). Originally inspired by the Hibernate Query Language (HQL), JPQL was standardized in 2006 as part of the Java Persistence API (JPA) under JSR-220 within the Enterprise JavaBeans 3.0 specification. Following Oracle's transfer of Java EE technologies to the Eclipse Foundation in 2017, JPA was rebranded as in 2019, with JPQL adopting its current name to reflect the namespace change from javax to jakarta. The language has evolved through multiple versions, including 3.0 (released in 2020) for initial jakarta namespace adoption, 3.1 (2022) with improved UUID and date/time functions, 3.2 (2024) adding JPQL support for set operations like UNION and Java records as embeddables, and 4.0 under development as of 2025. As of November 2025, Jakarta Query 1.0 is in early development to unify JPQL with query languages for non-relational stores.

Overview

Definition and Purpose

The Query Language (JPQL) is an SQL-like, object-oriented designed for retrieving and manipulating persistent objects within the framework. It enables developers to express queries against entities and their persistent state using an abstract schema that represents the , rather than directly referencing database tables or columns. The primary purpose of JPQL is to provide a portable and database-independent mechanism for querying entities, abstracting away vendor-specific SQL dialects and ensuring consistency across different data stores. By operating on the entity model defined in , JPQL allows queries to target managed objects within a persistence context, facilitating seamless integration with Java applications while maintaining relational database compatibility. JPQL's scope encompasses querying entities, their relationships, and associated values through support for SELECT, UPDATE, and DELETE statements, often augmented by clauses such as WHERE, GROUP BY, HAVING, and ORDER BY. This focus on abstract persistence schemas promotes object-relational mapping by navigating relationships via path expressions, without requiring knowledge of underlying database structures. While sharing syntactic similarities with SQL, JPQL emphasizes entity-centric operations to enhance developer productivity in enterprise environments.

Relation to Jakarta Persistence API

The Jakarta Persistence Query Language (JPQL) serves as the primary declarative query mechanism within the Jakarta Persistence API, with version 3.0 marking the rebranding from the Java Persistence API under the in 2019. This integration enables developers to write portable, object-oriented queries against entity classes without direct dependence on underlying database schemas, aligning with the API's goal of managing persistence and object-relational mapping in applications. JPQL queries are executed through the EntityManager interface, the central component of the Jakarta Persistence API for interacting with the persistence context. For instance, the createQuery(String qlString) method constructs a Query object from a JPQL string, while createQuery(String qlString, Class<T> resultClass) returns a typed TypedQuery for compile-time ; named queries can be predefined using the @NamedQuery annotation and invoked via createNamedQuery(String name). These queries support parameterization with named (:param) or positional (?1) placeholders, allowing dynamic execution through methods like setParameter and retrieval of results via getResultList() or getSingleResult(), which return managed entity instances integrated into the persistence context. In the persistence lifecycle, JPQL plays a key role in fetching and manipulating data within transactional boundaries, contrasting with imperative methods like EntityManager.find(Class<T> entityClass, Object primaryKey), which retrieve a single by ID directly from the cache or database without query parsing. JPQL operations synchronize with the persistence context to manage states (managed, detached, or removed), support JTA or resource-local transactions—requiring active transactions for execution to avoid exceptions like TransactionRequiredException—and leverage caching mechanisms, including the first-level persistence context cache and optional second-level shared cache configured via persistence.xml. Bulk UPDATE and DELETE statements in JPQL bypass instance management but still operate within transactions to ensure data consistency. At runtime, JPQL queries are parsed into an for validation against the entity metamodel and then translated by the persistence provider—such as Hibernate or EclipseLink—into native SQL tailored to the target database, incorporating joins and mappings derived from entity annotations or XML descriptors. This translation process ensures database portability while handling relationship navigation abstractly, without exposing SQL details to the application code.

History

Origins and Influences

The Jakarta Persistence Query Language (JPQL) originated in 2006 as an integral component of the Java Persistence API (JPA) 1.0 specification, developed under Java Specification Request (JSR) 220 to standardize object-relational mapping (ORM) querying within EE environments. This effort was led by the EJB 3.0 expert group, which sought to simplify persistence management by introducing a query mechanism tailored to entity objects rather than direct database tables. The language addressed the complexities of enterprise applications by providing an abstraction over underlying relational databases, enabling developers to work with persistent entities in a more intuitive, object-oriented manner. JPQL evolved from the limitations of earlier query approaches in the EJB specification, particularly the need for a non-SQL alternative to handle entity relationships and navigation without vendor-specific extensions. It built upon the Enterprise JavaBeans Query Language (EJB QL) introduced in EJB 2.0, extending its capabilities for greater expressiveness and portability across JPA implementations. A primary influence was the Hibernate Query Language (HQL) from Hibernate 2.x, released in the early 2000s, which popularized object-oriented querying by allowing queries against domain models instead of database schemas. The initial specification of JPQL was formalized in the final release of JSR 220 on May 11, 2006, marking its debut as a standardized tool to mitigate the portability issues and associated with raw SQL in persistence layers. This release emphasized JPQL's role in promoting database independence and simplifying complex joins and aggregations over graphs. Today, as part of the specification following the 2019 rebranding from JPA under the , JPQL continues to serve as the core query language for modern persistence.

Standardization and Version Evolution

The Jakarta Persistence Query Language (JPQL) was formally standardized as part of the Java Persistence API (JPA) 1.0 specification, approved by the Java Community Process (JCP) under JSR 220 on May 11, 2006. This initial standardization integrated JPQL as the primary query mechanism for entity-based operations, drawing from earlier influences like Hibernate Query Language while establishing a vendor-neutral syntax for object-relational mapping. In 2019, following Oracle's transfer of Java EE stewardship to the Eclipse Foundation, the specification transitioned to Jakarta Persistence, retaining JPQL unchanged under the new namespace as part of Jakarta EE 8, with Jakarta Persistence 2.2 released on August 8, 2019. Subsequent versions of the specification evolved JPQL through targeted enhancements while maintaining core compatibility. JPA 2.0, finalized on December 10, 2009 via JSR 317, introduced the Criteria API as a programmatic alternative to JPQL for building type-safe queries, complementing JPQL's string-based approach without altering its syntax. JPA 2.1, approved on May 22, 2013 under JSR 338, added support for tuple-based results in JPQL queries, enabling more flexible handling of multi-select outcomes such as constructor expressions and non-entity projections. The shift to Jakarta Persistence 3.0, released on September 8, 2020 for Jakarta EE 9, primarily focused on namespace updates from javax to jakarta, ensuring JPQL's seamless migration without functional changes to the language itself. Recent updates have further refined JPQL for modern Java ecosystems. Jakarta Persistence 3.1, finalized on March 4, 2022 for Jakarta EE 10, enhanced type safety by allowing Expressions as conditions in Criteria CASE statements and clarifying type mixing for query parameters, indirectly benefiting JPQL through improved metamodel integration. Jakarta Persistence 3.2, released on April 10, 2024 for Jakarta EE 11, introduced streamlined JPQL syntax for single-entity queries—omitting optional identification variables and SELECT clauses—and backported SQL-standard functions, including JSON scalar expressions for ORDER BY clauses, to broaden JPQL's expressiveness. As of November 2025, ongoing work under the Jakarta Query 1.0 specification (draft released October 2025) aims to unify JPQL with query languages from Jakarta Data and NoSQL, providing a common core for object-oriented querying across diverse data stores while extending JPQL for SQL-backed persistence. Throughout these evolutions, maintenance releases have prioritized backward compatibility, positioning JPQL as a stable, non-breaking core feature across JPA and Jakarta Persistence implementations.

Syntax Fundamentals

Basic Query Structure

The Jakarta Persistence Query Language (JPQL) defines queries using a structured syntax that resembles SQL but operates on entities rather than database tables. A basic JPQL query consists of a mandatory SELECT clause followed by a FROM clause, with optional WHERE, GROUP BY, HAVING, and ORDER BY clauses, expressed in the formal pattern: select_query ::= [select_clause] from_clause [where_clause] [groupby_clause] [having_clause] [orderby_clause]. Keywords in JPQL, such as SELECT and FROM, are case-insensitive, allowing flexible casing in query statements. Identification variables in JPQL serve as abstract names for entities or collections declared in the FROM clause, enabling navigation through object relationships. For instance, a simple query might use SELECT e FROM Employee e, where e is the identification variable ranging over the Employee entity type. These variables differ from SQL table aliases by focusing on entity instances rather than relational joins, and they are case-insensitive while avoiding reserved identifiers. The SELECT clause specifies the query's result, which can include single entity instances, collections of entities, scalar values from aggregate functions like COUNT or SUM, or constructor expressions that instantiate new objects. Constructor expressions, for example, take the form SELECT NEW com.example.Result(e.name, e.salary) FROM Employee e, allowing the creation of custom result objects directly in the query. Results may also return as tuples when multiple expressions are selected, such as Object[] arrays. JPQL supports parameterization to create dynamic, secure queries by substituting values at runtime, mitigating risks like . Parameters can be positional, denoted by ? followed by a number starting from 1 (e.g., ?1), or named, prefixed with a colon (e.g., :param), and cannot be mixed within the same query. Named parameters are case-sensitive and bound using methods like setParameter(String name, Object value), while positional ones use setParameter(int position, Object value). Both types accommodate single values or collections for flexible input handling.

Clauses and Expressions

The FROM clause in JPQL declares the root entities or collections that form the domain of the query, using an identification variable to reference them, such as FROM Order o where Order is the entity class and o is the variable. It supports multiple declarations separated by commas and enables joins through navigation of associations, for instance, FROM Order o JOIN o.items i to include related items in the query scope. Join types include inner joins via INNER JOIN (or simply JOIN), left outer joins with LEFT OUTER JOIN (or LEFT JOIN), right outer joins with RIGHT OUTER JOIN or RIGHT JOIN (introduced in Jakarta Persistence 3.2), cross joins with CROSS JOIN, and fetch joins using JOIN FETCH or LEFT JOIN FETCH to eagerly load associated entities and avoid issues. The WHERE clause specifies a conditional expression to filter the objects or values retrieved by the query, restricting results based on logic, such as WHERE o.total > 100. It supports arithmetic comparison operators like =, <>, >, <, >=, and <= for numeric or string comparisons; pattern matching with LIKE and wildcards (e.g., WHERE o.customerName LIKE 'A%'); membership tests via IN for lists or subqueries (e.g., WHERE o.status IN ('PENDING', 'SHIPPED')); range checks using BETWEEN (e.g., WHERE o.date BETWEEN :start AND :end); and null handling with IS NULL or IS NOT NULL. For collections, it includes tests like IS EMPTY to check if a collection-valued association has no elements (e.g., WHERE o.items IS EMPTY) and MEMBER OF to verify membership (e.g., WHERE :item MEMBER OF o.items). Logical operators AND, OR, and NOT combine these expressions, with parentheses for precedence. Path expressions in JPQL use dot notation to navigate from an identification variable to entity state fields or associations, resolving to single-valued attributes like e.department.name for a string value or collection-valued paths like e.orders when used in joins. Single-valued association paths, such as e.manager, treat the association as a reference to another entity instance, while collection paths require explicit joins to access elements. These expressions can appear in SELECT, WHERE, ORDER BY, and other clauses, enabling object-oriented navigation without direct table references. Aggregate functions in JPQL compute summary values over query results, including COUNT for counting entities or distinct values (e.g., COUNT(o.items) returning a Long), SUM for totaling numeric fields (e.g., SUM(o.total) as Long or BigDecimal based on type), AVG for averages (returning Double), and MIN or MAX for extrema (matching the field's type). The GROUP BY clause groups results by one or more state fields or single-valued paths (e.g., GROUP BY o.customer), allowing aggregates to operate per group, and must precede HAVING if used. The HAVING clause then filters these groups with a conditional expression involving aggregates (e.g., HAVING COUNT(o.items) > 5), applied after grouping. Finally, the ORDER BY clause sorts the results ascending (ASC, default) or descending (DESC) by paths or aggregates (e.g., ORDER BY AVG(o.total) DESC), supporting multiple items for multi-level sorting and NULLS FIRST or NULLS LAST options (introduced in Jakarta Persistence 3.2).

Key Differences from SQL

Abstraction and Entity Focus

The Jakarta Persistence Query Language (JPQL) provides a high level of by targeting classes and their attributes rather than underlying database tables and columns, enabling developers to write queries against the object-oriented . For instance, a query might select from an Employee entity using SELECT e FROM Employee e WHERE e.department = :dept, where Employee refers to the Java class annotated with @Entity, and attributes like department correspond to persistent fields defined via annotations such as @Column. This entity-centric approach abstracts away physical database details, such as table names or column mappings, allowing the same query to execute portably across different relational databases without modification. JPQL operates on the metamodel of the persistence unit, which represents the abstract schema of entities and their relationships as defined in the application's , ignoring implementation-specific schema elements like indexes, constraints, or storage engines. The metamodel is accessed through the Metamodel provided by the EntityManagerFactory, offering a type-safe view of entity types and attributes for query construction. This design promotes domain-driven queries that align closely with the application's , focusing on persistent state and relationships within the same persistence unit rather than low-level database structures. Annotations on entity classes, such as @Entity, @Id, and @ManyToOne, enable a type-safe domain model verifiable by the Java compiler and IDE tools for entity structure and relationships. However, JPQL queries are string-based and do not receive compile-time type checking for paths or syntax; verification occurs at runtime by the persistence provider or through IDE assistance. At runtime, the persistence provider—such as Hibernate or EclipseLink—translates the JPQL string into vendor-specific SQL, handling variations in database dialects while preserving the query's semantics. Direct access to database-specific functions is restricted in standard JPQL to maintain portability; instead, it supports a predefined set of portable functions like CONCAT or ABS, with extensions for custom functions only available through provider-specific mechanisms. Jakarta Persistence 3.2 (released April 2024) added new standard functions for math, string, and date/time operations, further enhancing portability. This abstraction layer ensures that queries remain focused on entities, fostering maintainable and database-agnostic code. In JPQL, navigation paths enable traversal of entity relationships using dot notation based on association fields defined in the entity mappings. Single-valued paths, such as o.[customer](/page/Customer).[address](/page/Address), allow direct access to related entities in one-to-one or many-to-one associations without requiring explicit joins, abstracting the underlying relational structure. In contrast, collection-valued paths, like those for one-to-many or many-to-many relationships (e.g., o.lineItems), cannot be further navigated directly and necessitate explicit JOIN clauses to access elements within the collection, ensuring type-safe querying over persistent collections. JPQL supports several join types to handle relationships explicitly, including INNER JOIN and LEFT JOIN for filtering and including related entities, respectively. FETCH JOIN extends this by eagerly loading associated entities or collections in a single SQL statement, which optimizes performance by mitigating the N+1 query problem—where multiple additional queries would otherwise be needed to fetch related data for each root entity. As of Jakarta Persistence 3.2, JPQL supports an explicit CROSS JOIN keyword similar to SQL for cartesian products; in earlier versions, these were achieved through comma-separated identification variables in the FROM clause. The same version introduced joins, allowing joins without predefined relationships, and support for UNION, INTERSECT, and EXCEPT operations, aligning JPQL more closely with SQL set operations while maintaining entity abstraction. It also enables streamlined syntax for simple single-entity queries, omitting the SELECT clause and identification variables (e.g., FROM Employee WHERE salary > 50000). This entity-focused approach builds on JPQL's abstraction benefits, allowing queries to operate seamlessly over object graphs without manual table aliasing. Relationship handling in JPQL accommodates one-to-one, one-to-many, and many-to-many associations as defined in entity mappings, with bidirectional relationships using the mappedBy attribute on the inverse side to reference the owning side's field. For example, in a many-to-many relationship between entities like Order and Product, navigation might involve JOIN o.products p to query across the join table implicitly. Polymorphic queries on inheritance hierarchies are supported, enabling treatment of subclasses; the TREAT expression facilitates downcasting, as in SELECT t FROM Entity e JOIN TREAT(e AS Subclass) t, allowing queries to access subclass-specific attributes within the inheritance chain. The following example illustrates a FETCH JOIN for a one-to-many relationship:

SELECT d FROM Department d LEFT JOIN FETCH d.employees

SELECT d FROM Department d LEFT JOIN FETCH d.employees

This query loads departments and their employees in one execution, avoiding separate fetches.

Practical Examples

Simple Select Queries

Simple select queries in the Jakarta Persistence Query Language (JPQL) form the foundation for retrieving data from classes, adhering to the basic structure of SELECT, FROM, and optional WHERE clauses. These queries operate on abstract schema types rather than database tables, enabling object-oriented data access. A basic entity selection query retrieves entire instances based on a simple condition. For example, the query SELECT e FROM Employee e WHERE e.salary > 50000 selects all Employee entities where the salary attribute exceeds 50,000, returning a list of complete Employee objects. This approach leverages the FROM clause to identify the root (Employee) and an identification variable (e) for reference, while the WHERE clause applies a conditional filter using entity attributes. Scalar projection allows retrieval of specific attributes or a subset of fields, reducing data transfer and improving efficiency. The query SELECT e.name, e.id FROM Employee e returns only the name and id fields from all Employee entities as an Object[] array for each result row. This projection syntax in the SELECT clause specifies paths to individual state fields, avoiding the overhead of full hydration. Parameterized queries enhance reusability and security by binding values at runtime, preventing risks inherent in string concatenation. Consider SELECT e FROM Employee e WHERE e.department = :dept; this uses a :dept, which is bound via query.setParameter("dept", departmentValue) on the Query object. Positional parameters like ?1 can alternatively be used with setParameter(1, value). Execution of simple select queries occurs through the EntityManager interface, which parses and processes the JPQL string. A dynamic query is created with EntityManager em = ...; TypedQuery<Employee> query = em.createQuery("SELECT e FROM Employee e WHERE e.salary > 50000", Employee.class); List<Employee> results = query.getResultList();, yielding a list of entities or scalars. For single results, getSingleResult() is invoked instead, throwing NoResultException if none match or NonUniqueResultException if multiple do.

Queries with Joins and Aggregates

In JPQL, joins enable the navigation of entity relationships to retrieve from multiple related in a single query, supporting inner joins, left outer joins, and fetch joins for optimized loading. For instance, an inner join can be used to select orders along with their associated from a specific location, as in the query SELECT o, c FROM Order o JOIN o.[customer](/page/Customer) c WHERE c.city = '[Jakarta](/page/Jakarta)'. This query returns a list of Object arrays, where each array contains an Order entity and its corresponding entity, allowing for multi-entity results that leverage the object model's paths. Aggregates in JPQL provide functions such as COUNT, SUM, AVG, MAX, and MIN to perform computations over sets of entities, often combined with joins and grouping for summarized insights. A typical aggregate query might compute departmental statistics, exemplified by SELECT d, AVG(e.salary) FROM Department d JOIN d.employees e GROUP BY d HAVING AVG(e.salary) > 60000. Here, the GROUP BY clause partitions results by department, while the HAVING clause filters groups based on the average salary condition, ensuring only qualifying departments are returned as Object arrays containing the Department entity and the computed average. Fetch joins extend standard joins by eagerly loading related entities to avoid the N+1 query problem, initializing associations in a single database round-trip. An example is SELECT o FROM Order o FETCH JOIN o.items, which retrieves orders with their line items pre-fetched, returning a list of Order entities where the items collection is already populated. For type-safe execution of such multi-select or aggregate queries, developers can use the TypedQuery interface with generics, such as TypedQuery<Object[]>, to ensure compile-time checking of result types.

Hibernate Query Language

The Hibernate Query Language (HQL) is Hibernate's native object-oriented , designed as a superset of the Jakarta Persistence Query Language (JPQL) to provide enhanced querying capabilities within the Hibernate ORM framework. Introduced as a superset of JPQL starting with Hibernate 3.2 in 2006, HQL maintains full compatibility with JPQL queries while extending them with Hibernate-specific features that leverage the underlying database's capabilities. This allows developers to write JPQL-compliant queries that execute seamlessly in HQL mode, but HQL enables more advanced operations tailored to Hibernate's implementation details. Key extensions in HQL beyond the JPQL standard include support for native SQL functions, which permit the use of database-specific operations directly in queries. For instance, developers can register and invoke functions like PostgreSQL's case-insensitive ILIKE via Hibernate's function registration mechanism or the function() construct, enabling patterns such as where function('ilike', e.name, :pattern). HQL also supports dynamic fetching strategies through the join fetch directive, which eagerly loads associated entities in a single query to mitigate the N+1 select problem; while JOIN FETCH is standardized in JPQL since JPA , HQL provides additional flexibility in Hibernate implementations. Additionally, HQL-specific hints, such as those for query optimization or database-specific execution plans (e.g., embedding hints via comments or custom SQL), allow fine-tuned control over query behavior that JPQL lacks. These extensions make HQL particularly useful for performance-critical applications where standard JPQL abstractions fall short. In Hibernate, HQL queries are executed primarily through the Session.createQuery() method, which accepts an HQL string and optional result type for type-safe execution; for example:

java

List<Book> books = session.createQuery("from Book b join fetch b.author where b.title like :title", Book.class) .setParameter("title", "%Java%") .getResultList();

List<Book> books = session.createQuery("from Book b join fetch b.author where b.title like :title", Book.class) .setParameter("title", "%Java%") .getResultList();

This method supports a JPQL compatibility mode via configuration properties like hibernate.jpa.compliance.query=true, ensuring JPQL queries run without modification. HQL also integrates with entity-named queries defined using the @NamedQuery annotation, allowing reusable, parameterized queries stored directly on entity classes for better maintainability. A notable difference is HQL's handling of path expressions for navigating mapped entity relationships, such as from Order o where o.customer.address.city = 'Jakarta', which automatically generates joins along the association chain. Furthermore, HQL offers flexible polymorphism handling in queries on inheritance hierarchies, including support for the with clause in conditional joins (e.g., from Person p left join p.addresses a with a.type = 'home'); this feature, available in HQL earlier, has been standardized in JPQL since JPA 2.1. In recent versions of (e.g., 3.0 released in 2022 and 3.1 in 2023), JPQL has gained new operators and functions, while HQL continues to extend these with Hibernate-specific enhancements, maintaining its role as a powerful dialect.

Criteria API as an Alternative

The was introduced in JPA 2.0 in 2009 as a programmatic alternative to JPQL for constructing queries in a type-safe manner. It enables developers to build queries using Java objects and methods rather than textual strings, leveraging the JPA metamodel to ensure compile-time verification of entity attributes and relationships. Central to this are the CriteriaBuilder interface, which serves as a for creating query components such as expressions and predicates, and the CriteriaQuery interface, which defines the overall query structure including selection, from clauses, and ordering. Key features of the Criteria API include support for to assemble query clauses dynamically, allowing for flexible construction based on runtime conditions. For instance, predicates can be created using methods like cb.equal(root.get("name"), param) to form conditions without string concatenation. It also facilitates programmatic handling of joins, path navigation through entity relationships, and aggregation functions, all while maintaining portability across JPA providers. Queries built with the Criteria API return a TypedQuery object, which provides type-safe execution and result handling. Compared to JPQL's textual and SQL-like , the Criteria API offers significant advantages by eliminating runtime errors from syntax mistakes or typos in query strings, promoting better integration with modern features such as lambdas for predicate composition since Java 8. This approach is particularly beneficial for dynamic queries where conditions vary at runtime, as it allows conditional logic to be expressed directly in code. While equivalent in expressiveness to JPQL, the Criteria API avoids the pitfalls of string-based queries, making it ideal for complex, adaptable persistence operations within . Recent enhancements in 3.0 (2022) and later include improved support for entity graphs and in Criteria queries.

Advanced Features

Functions and Operators

The Jakarta Persistence Query Language (JPQL) provides a set of built-in functions and operators to manipulate and compare data within queries, enabling operations on entity attributes, path expressions, and literals while abstracting underlying database specifics. These functions and operators are defined in the specification to support arithmetic calculations, string processing, temporal extractions, collection inspections, and aggregations, with conditional logic via CASE expressions. They integrate seamlessly into SELECT, WHERE, and ORDER BY clauses to refine query results. Arithmetic operators in JPQL include (+), (-), (*), division (/), and (%), applicable to numeric state fields or path expressions. These operators follow standard precedence rules, with unary operators evaluated before binary ones, and parentheses can override order. For instance, computes the remainder of division, such as price % 10 to find values ending in a specific digit. Division of s performs division, yielding an result. For floating-point results, cast at least one to a floating-point type (e.g., using provider-specific functions). Operations involving null values propagate null. Conditional arithmetic is supported through CASE expressions, which allow logic, as in CASE WHEN condition THEN value1 ELSE value2 END. String functions facilitate text manipulation, including CONCAT(string1, string2) for joining strings, SUBSTRING(string, start, length) for extraction, TRIM([BOTH|LEADING|TRAILING] [char] FROM string) for removing whitespace or specified characters, UPPER(string) and LOWER(string) for case conversion, and LENGTH(string) for character count. Jakarta Persistence 3.2 introduces additional functions like LEFT(string, length) to extract initial characters, RIGHT(string, length) for trailing ones, and REPLACE(string, from, to) for substitutions, enhancing SQL compatibility. String concatenation can also use the || operator in 3.2, such as firstName || ' ' || lastName. These functions return string or integer types and handle null inputs by yielding null. Date and time functions provide access to current values and temporal components, with CURRENT_DATE, CURRENT_TIME, and CURRENT_TIMESTAMP returning the respective java.sql types. Enhanced support for java.time types in Jakarta Persistence 3.1 includes extraction functions like YEAR(temporal), MONTH(temporal) (numbered 1-12), QUARTER(temporal) (1-4), and WEEK(temporal), applicable to fields of type LocalDate, LocalDateTime, or similar. These enable queries filtering by calendar periods, such as extracting the year from an entity's creation date. Null temporal inputs result in null outputs. Collection functions complement these by offering SIZE(collection) to return the integer count of elements in a persistent collection-valued association and MEMBER OF(value, collection) as a boolean check for membership. Aggregate functions perform computations over groups of entities or values, including COUNT(*), COUNT(DISTINCT path) for counting rows or unique values (returning Long), SUM(path) for totaling numerics (, BigInteger, or BigDecimal based on type), AVG(path) for (Double), MAX(path), and MIN(path) for extrema (matching the field's type). Aggregates ignore null values by default, except COUNT which includes all rows. DISTINCT ensures uniqueness in COUNT, SUM, AVG, MAX, and MIN, preventing duplicates from affecting results. These functions are typically used in SELECT clauses with GROUP BY for summarization, often integrated into WHERE conditions for filtering aggregated data.

Bulk Update and Delete Operations

The Jakarta Persistence Query Language (JPQL) supports bulk update and delete operations to efficiently modify or remove multiple entities in a single database statement, without loading them into the persistence context. These operations are particularly useful for administrative tasks or where individual entity management is unnecessary. Introduced in JPA 2.0, they enable direct database-level changes while adhering to JPQL's abstract schema syntax. The syntax for a bulk update statement follows the form: UPDATE abstract_schema_name [[AS] identification_variable] SET update_item {, update_item}* [WHERE update_condition], where abstract_schema_name refers to the entity class, update_item assigns values to state fields or single-valued , and the optional WHERE clause filters the affected entities. For example, to increase salaries for employees in a specific department: UPDATE Employee e SET e.salary = e.salary * 1.1 WHERE e.department.name = :deptName. This supports parameterization for safe value binding, such as using named parameters like :deptName. However, update statements are limited to simple state field paths in the SET clause and, in early JPA versions like , do not permit subqueries in the WHERE clause or joins in the update expression. Bulk delete operations use the syntax: DELETE FROM abstract_schema_name [[AS] identification_variable] [WHERE delete_condition], targeting entities that match the optional WHERE condition. An example is DELETE FROM Player p WHERE p.status = 'inactive' AND p.teams IS EMPTY, which removes inactive players without associated teams. Like updates, deletes support parameters in the WHERE clause but bypass entity-level cascading; for cascading deletes, developers must rely on entity relationship annotations such as CascadeType.REMOVE when removing individual entities via the EntityManager. To execute these operations, create a Query or TypedQuery via the EntityManager and invoke executeUpdate(), which returns an integer representing the number of affected database rows. For instance:

java

Query query = entityManager.createQuery("UPDATE Employee e SET e.salary = e.salary * 1.1 WHERE e.department.name = :deptName"); query.setParameter("deptName", "Sales"); int updatedRows = query.executeUpdate();

Query query = entityManager.createQuery("UPDATE Employee e SET e.salary = e.salary * 1.1 WHERE e.department.name = :deptName"); query.setParameter("deptName", "Sales"); int updatedRows = query.executeUpdate();

This method commits changes directly to the database, circumventing the persistence context and potentially detaching or invalidating any managed entities affected by the operation. To synchronize the context afterward, call EntityManager.refresh() on specific entities or EntityManager.clear() to detach all, ensuring subsequent queries reflect the updates. Bulk operations should typically run in a new transaction or isolated context to avoid inconsistencies with loaded entities.

References

Add your contribution
Related Hubs
User Avatar
No comments yet.