JPA Query Language (JPQL) Basics: A Complete Guide for Beginners and Professionals

Illustration for JPA Query Language (JPQL) Basics: A Complete Guide for Beginners and Professionals
By Last updated:

The Java Persistence API (JPA) provides a robust way to map Java objects to relational database tables. While basic CRUD operations are straightforward, real-world applications demand dynamic querying, filtering, and joining across entities.

This is where the Java Persistence Query Language (JPQL) comes into play. JPQL is object-oriented, meaning you query entities, not tables. It provides database portability while offering SQL-like syntax tailored for Java applications.

In this tutorial, we’ll explore JPQL basics, complete with examples, CRUD operations, joins, best practices, and pitfalls to avoid.


What is JPQL?

JPQL is the query language of JPA. Unlike SQL, which works directly on database tables, JPQL works with entity objects and their fields.

Example Difference:

  • SQL:

    SELECT * FROM employees e WHERE e.emp_name = 'John';
    
  • JPQL:

    SELECT e FROM Employee e WHERE e.name = 'John'
    

Here, JPQL queries the Employee entity, not the employees table.


Setting Up JPA for JPQL Queries

<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
    version="3.0">
    <persistence-unit name="examplePU">
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <class>com.example.Employee</class>
        <properties>
            <property name="jakarta.persistence.jdbc.url" value="jdbc:h2:mem:testdb"/>
            <property name="jakarta.persistence.jdbc.driver" value="org.h2.Driver"/>
            <property name="jakarta.persistence.jdbc.user" value="sa"/>
            <property name="hibernate.hbm2ddl.auto" value="update"/>
            <property name="hibernate.show_sql" value="true"/>
        </properties>
    </persistence-unit>
</persistence>

Entity Example for JPQL

import jakarta.persistence.*;

@Entity
@Table(name = "employees")
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    private String department;
    private Double salary;

    // getters and setters
}

Basic JPQL Queries

1. Select All

List<Employee> employees = entityManager
    .createQuery("SELECT e FROM Employee e", Employee.class)
    .getResultList();

2. Filtering with WHERE

List<Employee> employees = entityManager
    .createQuery("SELECT e FROM Employee e WHERE e.department = :dept", Employee.class)
    .setParameter("dept", "HR")
    .getResultList();

3. Ordering Results

List<Employee> employees = entityManager
    .createQuery("SELECT e FROM Employee e ORDER BY e.salary DESC", Employee.class)
    .getResultList();

4. Aggregates (COUNT, SUM, AVG)

Long count = entityManager
    .createQuery("SELECT COUNT(e) FROM Employee e", Long.class)
    .getSingleResult();

JPQL Joins

Inner Join

SELECT e FROM Employee e JOIN e.department d WHERE d.name = 'IT'

Left Join

SELECT e FROM Employee e LEFT JOIN e.department d

Fetch Join

SELECT e FROM Employee e JOIN FETCH e.department

Note: Fetch joins help prevent the N+1 select problem.


JPQL CRUD Operations

Create (Persist)

Employee emp = new Employee();
emp.setName("Alice");
emp.setDepartment("Finance");
emp.setSalary(80000.0);

entityManager.getTransaction().begin();
entityManager.persist(emp);
entityManager.getTransaction().commit();

Read (Find)

Employee emp = entityManager.find(Employee.class, 1L);

Update (Merge)

entityManager.getTransaction().begin();
emp.setSalary(90000.0);
entityManager.merge(emp);
entityManager.getTransaction().commit();

Delete (Remove)

entityManager.getTransaction().begin();
entityManager.remove(emp);
entityManager.getTransaction().commit();

Real-World Use Cases

  • Reporting dashboards → Aggregated salary reports, employee counts.
  • E-commerce → Query orders, customers, products with joins.
  • Banking apps → Audit trails and transaction lookups.

Performance Considerations

  • Use JOIN FETCH to avoid N+1 queries.
  • Apply pagination with setFirstResult and setMaxResults.
  • Prefer named queries for reusable queries.

Anti-Patterns and Pitfalls

  • Avoid SELECT * → Always specify fields.
  • Don’t use EnumType.ORDINAL with enums in queries.
  • Be cautious with eager fetching → may load unnecessary data.

Best Practices

  • Use parameters (:param) instead of concatenating strings.
  • Define Named Queries for frequently used queries.
  • Monitor SQL output for performance tuning.
  • Always prefer JOIN FETCH for associations when needed.

📌 JPA Version Notes

  • JPA 2.0 → Introduced Criteria API for type-safe queries.
  • JPA 2.1 → Added entity graphs, stored procedures.
  • JPA 2.2 → Added Java 8 time support.
  • Jakarta Persistence → Package renamed (javax.persistencejakarta.persistence).

Conclusion and Key Takeaways

  • JPQL is an object-oriented query language for entities, not tables.
  • It supports filtering, ordering, joins, and aggregates.
  • Performance tuning (JOIN FETCH, pagination) is essential in production.
  • Always use best practices like named queries and parameter binding.

FAQ: Expert-Level Questions

1. What’s the difference between JPA and Hibernate?
JPA is a specification; Hibernate is one of its implementations.

2. How does JPA handle the persistence context?
It’s like a classroom attendance register, tracking managed entities.

3. What are the drawbacks of eager fetching in JPA?
It loads all associations upfront, wasting memory and bandwidth.

4. How can I solve the N+1 select problem with JPA?
By using JOIN FETCH, batch fetching, or entity graphs.

5. Can I use JPA without Hibernate?
Yes, EclipseLink, OpenJPA, and DataNucleus are alternatives.

6. What’s the best strategy for inheritance mapping in JPA?
Depends on needs: SINGLE_TABLE (fastest), JOINED (normalized), TABLE_PER_CLASS (rare).

7. How does JPA handle composite keys?
Using @IdClass or @EmbeddedId annotations.

8. What changes with Jakarta Persistence?
Package names switched to jakarta.persistence.

9. Is JPA suitable for microservices?
Yes, but for ultra-high-performance microservices, lighter tools may be better.

10. When should I avoid using JPA?
When low-level performance or complex reporting queries are required.