Cheat SheetsHibernate & JPAQuerying

Querying — Cheat Sheet

Hibernate & JPA · 5 topics. Download the PDF or the Instagram carousel and share it.

Cheat Sheet · AiCanCode.org
Querying
Hibernate & JPA5 topicsQuick revision reference
1

JPQL Basics

JPQL is an entity-oriented query language; queries reference entity and field names (not table/column names), making them portable across DB vendors.

  • JPQL uses entity class names and Java field names — never SQL table or column names.
  • Use named parameters (:paramName) in JPQL — @Param("name") maps the argument; positional parameters (?1) are fragile.
  • SELECT new com.example.MyDto(o.id, o.name) constructor expression maps results to a DTO without loading full entities.
  • @Modifying + @Transactional is required for @Query UPDATE and DELETE; omitting either causes an exception.
  • JPQL JOIN automatically follows entity relationships (mappedBy, @JoinColumn) — no ON clause needed.
  • For DB-specific features (window functions, CTEs, MATCH AGAINST) use nativeQuery=true — JPQL cannot express everything SQL can.
Java — JPQL EntityManager
// SQL reference for comparison:
// SELECT id, total, status FROM orders o WHERE o.customer_id = 42 AND o.status = 'SHIPPED'

// Equivalent JPQL — uses class name "Order", field names "customerId", "status"
// Entity:  @Entity public class Order { Long id; Long customerId; String status; BigDecimal total; }
// "o" is an alias (like SQL AS)
String jpql = "SELECT o FROM Order o WHERE o.customerId = :cid AND o.status = :status";

// Executing with EntityManager
TypedQuery<Order> query = em.createQuery(jpql, Order.class);
query.setParameter("cid",    42L);
query.setParameter("status", "SHIPPED");
List<Order> orders = query.getResultList();

// SELECT single field (returns List<BigDecimal>)
TypedQuery<BigDecimal> totalsQuery =
    em.createQuery("SELECT o.total FROM Order o WHERE o.customerId = :cid", BigDecimal.class);
totalsQuery.setParameter("cid", 42L);
List<BigDecimal> totals = totalsQuery.getResultList();

// Aggregate functions
TypedQuery<Long> countQuery =
    em.createQuery("SELECT COUNT(o) FROM Order o WHERE o.status = :s", Long.class);
countQuery.setParameter("s", "PENDING");
long count = countQuery.getSingleResult();
2

JPQL Joins & Fetch Joins

Regular JPQL JOIN does not initialise the lazy collection; JOIN FETCH forces Hibernate to load it in the same query, preventing N+1 selects for that association.

  • JOIN in JPQL filters but does NOT initialise the lazy association (N+1 still occurs on access).
  • JOIN FETCH loads the association in the same SQL query — eliminates N+1 for that path.
  • JOIN FETCH on a collection + Pageable triggers in-memory pagination — use @EntityGraph instead.
  • Fetching two List collections in one query causes MultipleBagFetchException.
  • Fix: change List to Set, or load each collection in a separate query.
  • LEFT JOIN FETCH includes the parent even when the child collection is empty.
Java — JOIN vs JOIN FETCH
// Regular JOIN — use for filtering only, does NOT initialise the collection
@Query("SELECT o FROM Order o " +
       "JOIN o.customer c " +
       "WHERE c.tier = 'GOLD'")
List<Order> findGoldCustomerOrders();
// SQL: SELECT o.* FROM orders o JOIN customers c ON o.customer_id=c.id WHERE c.tier='GOLD'
// Accessing order.getCustomer() later triggers N lazy selects!

// JOIN FETCH — loads the association in the SAME query
@Query("SELECT o FROM Order o " +
       "JOIN FETCH o.customer " +
       "WHERE o.status = :status")
List<Order> findWithCustomerByStatus(@Param("status") OrderStatus status);
// SQL: SELECT o.*, c.* FROM orders o JOIN customers c ON o.customer_id=c.id WHERE o.status=?
// Accessing order.getCustomer() hits the L1 cache — no additional SQL

// LEFT JOIN FETCH — include orders even if customer is null
@Query("SELECT o FROM Order o LEFT JOIN FETCH o.customer WHERE o.id = :id")
Optional<Order> findWithOptionalCustomer(@Param("id") Long id);
3

Named Queries

@NamedQuery precompiles JPQL at startup, enabling early syntax validation and potential query plan caching; defined on the entity class for discoverability.

  • @NamedQuery JPQL is validated at startup — syntax errors fail fast, not at runtime
  • SQL translation is cached per named query — no re-parsing cost per execution
  • Convention: name = "EntityName.descriptiveName" for Spring Data auto-detection
  • Spring Data looks for a matching @NamedQuery before generating a derived query
  • @NamedNativeQuery holds raw SQL; use @SqlResultSetMapping to map results to a DTO
  • With Spring Data @Query, explicit inline queries are often clearer than @NamedQuery
Java — @NamedQuery definition on entity
@Entity
@Table(name = "orders")
@NamedQueries({
    @NamedQuery(
        name  = "Order.findByCustomer",
        query = "SELECT o FROM Order o WHERE o.customerId = :customerId ORDER BY o.createdAt DESC"
    ),
    @NamedQuery(
        name  = "Order.countPendingByCustomer",
        query = "SELECT COUNT(o) FROM Order o WHERE o.customerId = :customerId AND o.status = 'PENDING'"
    )
})
public class Order {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Long customerId;
    private String status;
    private LocalDateTime createdAt;
}
4

Criteria API

The Criteria API builds type-safe dynamic queries programmatically using metamodel classes; more verbose than JPQL but catches column/type errors at compile time.

  • Criteria API provides compile-time type safety; JPQL strings fail at runtime when field names change.
  • The Hibernate JPA metamodel generator (hibernate-jpamodelgen) produces entity_class_ files with SingularAttribute/ListAttribute fields.
  • Build predicates conditionally and combine with cb.and() for dynamic optional-filter search endpoints.
  • Spring Data Specifications are the recommended abstraction — cleaner than raw Criteria, composable with .and()/.or().
  • CriteriaQuery<T> vs CriteriaQuery<Tuple>: use Tuple for multi-column projections that do not map to a single entity.
  • For very complex reports, native SQL is often simpler than deep Criteria nesting — choose the right tool for readability.
Java — metamodel setup + basic Criteria query
<!-- pom.xml — metamodel generator -->
<dependency>
  <groupId>org.hibernate.orm</groupId>
  <artifactId>hibernate-jpamodelgen</artifactId>
  <version>6.4.0.Final</version>
  <scope>provided</scope>
</dependency>

// Generated: Order_.java (do not edit — auto-generated)
// @StaticMetamodel(Order.class)
// public abstract class Order_ {
//     public static volatile SingularAttribute<Order, Long>   id;
//     public static volatile SingularAttribute<Order, String> status;
//     public static volatile SingularAttribute<Order, BigDecimal> amount;
//     public static volatile ListAttribute<Order, OrderItem>  items;
// }

// Basic Criteria query
public List<Order> findByStatus(String status) {
    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<Order> query = cb.createQuery(Order.class);
    Root<Order> root = query.from(Order.class);

    query.select(root)
         .where(cb.equal(root.get(Order_.status), status))
         .orderBy(cb.desc(root.get(Order_.amount)));

    return em.createQuery(query)
             .setMaxResults(100)
             .getResultList();
}
5

Native SQL Queries

@Query(nativeQuery=true) sends raw SQL to the DB; useful for DB-specific features or complex queries that JPQL cannot express; results can map to entities or projections.

  • nativeQuery=true sends raw SQL — bypasses JPQL and uses DB-specific features directly
  • Always use named (:param) or positional (?1) bind parameters — never concatenate strings
  • Result mapping: entity class, projection interface, or @SqlResultSetMapping
  • Pagination requires an explicit countQuery — Spring Data cannot derive it from complex SQL
  • Native queries bypass Hibernate's entity graph — no automatic lazy loading of associations
  • Use JPQL for portability; native SQL when JPQL cannot express the required query
Java — @Query nativeQuery with entity and projection mapping
public interface OrderRepository extends JpaRepository<Order, Long> {

    // Maps result columns to Order entity fields
    @Query(value = "SELECT * FROM orders WHERE customer_id = :customerId AND status = :status",
           nativeQuery = true)
    List<Order> findByCustomerAndStatus(@Param("customerId") Long customerId,
                                        @Param("status") String status);

    // Projection interface — selects subset of columns
    @Query(value = "SELECT id, status, total_amount FROM orders WHERE customer_id = :customerId",
           nativeQuery = true)
    List<OrderSummary> findSummariesByCustomer(@Param("customerId") Long customerId);
}

// Projection interface
public interface OrderSummary {
    Long getId();
    String getStatus();
    BigDecimal getTotalAmount();
}
Learn this free with Aria, your AI tutor → AiCanCode.org/learn/hibernate