Criteria API

Learn how to build queries programmatically using Hibernate's Criteria API. This approach is useful for constructing dynamic queries based on runtime conditions. We'll cover the basics of building criteria queries and executing them.


Hibernate Criteria API Joins

This document explains how to perform joins between entities in your Criteria queries using Java Hibernate. We'll cover inner joins, outer joins, and fetch joins, with examples demonstrating one-to-one, one-to-many, and many-to-many relationships.

What are Joins?

In relational databases, a join combines rows from two or more tables based on a related column. This allows you to retrieve data that spans multiple entities in your object model. Hibernate's Criteria API provides a way to express these joins in a type-safe and object-oriented manner.

Types of Joins

Hibernate supports different types of joins, each determining which rows are included in the result set:

  • Inner Join: Returns rows only when there is a match in both tables based on the join condition.
  • Left Outer Join: Returns all rows from the left table (the table specified before the join keyword) and the matched rows from the right table. If there is no match in the right table, null values are returned for the right table's columns.
  • Right Outer Join: Returns all rows from the right table and the matched rows from the left table. If there is no match in the left table, null values are returned for the left table's columns. (Less commonly used)
  • Full Outer Join: Returns all rows when there is a match in one of the tables. If there is no match, the missing side will contain null. (Not directly supported by all databases).
  • Fetch Join: A specialized join that retrieves associated entities in a single query. It optimizes performance by avoiding the N+1 select problem. Fetch joins are often used to pre-fetch related data eagerly.

Performing Joins in Criteria Queries

Hibernate's CriteriaBuilder and Root interfaces are central to defining joins within Criteria queries.

Here's a general outline of how joins are performed:

  1. Obtain a CriteriaBuilder and a CriteriaQuery from the Session.
  2. Create a Root representing the main entity.
  3. Use the join() method of the Root to specify the join, its type (inner, left outer, etc.), and the association to join on.
  4. Optionally, add predicates (where clauses) to the join.
  5. Construct the TypedQuery and execute it.

Example Entities

Let's assume the following entities with different relationships:

  • Author: Has a one-to-many relationship with Books.
  • Book: Has a many-to-one relationship with Author and a one-to-one relationship with a PublisherInfo.
  • PublisherInfo: One-to-one with Book
  • Category: Has a many-to-many relationship with Books.

Here are simplified Java entity definitions:

 @Entity
    public class Author {
        @Id
        @GeneratedValue
        private Long id;
        private String name;

        @OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
        private List<Book> books = new ArrayList<>();

        // Getters and setters...
    }

    @Entity
    public class Book {
        @Id
        @GeneratedValue
        private Long id;
        private String title;

        @ManyToOne
        @JoinColumn(name = "author_id")
        private Author author;

        @ManyToMany
        @JoinTable(
            name = "book_category",
            joinColumns = @JoinColumn(name = "book_id"),
            inverseJoinColumns = @JoinColumn(name = "category_id")
        )
        private List<Category> categories = new ArrayList<>();

        @OneToOne(cascade = CascadeType.ALL)
        @JoinColumn(name = "publisher_info_id")
        private PublisherInfo publisherInfo;

        // Getters and setters...
    }

    @Entity
    public class Category {
        @Id
        @GeneratedValue
        private Long id;
        private String name;

        @ManyToMany(mappedBy = "categories")
        private List<Book> books = new ArrayList<>();

        // Getters and setters...
    }

    @Entity
    public class PublisherInfo {
        @Id
        @GeneratedValue
        private Long id;
        private String publisherName;

        // Getters and setters...
    } 

Criteria Join Examples

1. Inner Join (One-to-Many: Author and Books)

Find all books written by a specific author (inner join between Author and Book).

 import jakarta.persistence.criteria.*;
    import org.hibernate.Session;
    import org.hibernate.SessionFactory;

    // ... (SessionFactory creation and session opening omitted for brevity)

    Session session = sessionFactory.openSession();
    CriteriaBuilder cb = session.getCriteriaBuilder();
    CriteriaQuery<Book> cq = cb.createQuery(Book.class);
    Root<Book> bookRoot = cq.from(Book.class);

    // Inner join to Author using the 'author' association in the Book entity
    Join<Book, Author> authorJoin = bookRoot.join("author");

    // Predicate: Author's name is "John Doe"
    Predicate authorNamePredicate = cb.equal(authorJoin.get("name"), "John Doe");

    cq.select(bookRoot).where(authorNamePredicate);

    TypedQuery<Book> query = session.createQuery(cq);
    List<Book> books = query.getResultList();

    books.forEach(b -> System.out.println(b.getTitle()));

    session.close(); 

2. Left Outer Join (One-to-Many: Author and Books)

Find all authors and their books, including authors who have not written any books.

 import jakarta.persistence.criteria.*;
    import org.hibernate.Session;
    import org.hibernate.SessionFactory;

    // ... (SessionFactory creation and session opening omitted for brevity)

    Session session = sessionFactory.openSession();
    CriteriaBuilder cb = session.getCriteriaBuilder();
    CriteriaQuery<Object[]> cq = cb.createQuery(Object[].class); // Select an array of objects
    Root<Author> authorRoot = cq.from(Author.class);

    // Left outer join to Book using the 'books' association in the Author entity
    Join<Author, Book> bookJoin = authorRoot.join("books", JoinType.LEFT);

    cq.multiselect(authorRoot, bookJoin); // Select both Author and Book

    TypedQuery<Object[]> query = session.createQuery(cq);
    List<Object[]> results = query.getResultList();

    for (Object[] result : results) {
        Author author = (Author) result[0];
        Book book = (Book) result[1]; // Can be null if the author has no books

        System.out.println("Author: " + author.getName());
        if (book != null) {
            System.out.println("  Book: " + book.getTitle());
        } else {
            System.out.println("  No books for this author");
        }
    }
    session.close(); 

3. Inner Join (One-to-One: Book and PublisherInfo)

Find all books that have specific publisher info.

 import jakarta.persistence.criteria.*;
    import org.hibernate.Session;
    import org.hibernate.SessionFactory;

    // ... (SessionFactory creation and session opening omitted for brevity)

    Session session = sessionFactory.openSession();
    CriteriaBuilder cb = session.getCriteriaBuilder();
    CriteriaQuery<Book> cq = cb.createQuery(Book.class);
    Root<Book> bookRoot = cq.from(Book.class);

    // Inner join to PublisherInfo using the 'publisherInfo' association in the Book entity
    Join<Book, PublisherInfo> publisherInfoJoin = bookRoot.join("publisherInfo");

    Predicate publisherNamePredicate = cb.equal(publisherInfoJoin.get("publisherName"), "Acme Publishing");

    cq.select(bookRoot).where(publisherNamePredicate);

    TypedQuery<Book> query = session.createQuery(cq);
    List<Book> books = query.getResultList();

    books.forEach(b -> System.out.println(b.getTitle()));

    session.close(); 

4. Inner Join (Many-to-Many: Book and Category)

Find all books belonging to a specific category.

 import jakarta.persistence.criteria.*;
    import org.hibernate.Session;
    import org.hibernate.SessionFactory;

    // ... (SessionFactory creation and session opening omitted for brevity)

    Session session = sessionFactory.openSession();
    CriteriaBuilder cb = session.getCriteriaBuilder();
    CriteriaQuery<Book> cq = cb.createQuery(Book.class);
    Root<Book> bookRoot = cq.from(Book.class);

    // Inner join to Category using the 'categories' association in the Book entity
    Join<Book, Category> categoryJoin = bookRoot.join("categories");

    Predicate categoryNamePredicate = cb.equal(categoryJoin.get("name"), "Science Fiction");

    cq.select(bookRoot).where(categoryNamePredicate);

    TypedQuery<Book> query = session.createQuery(cq);
    List<Book> books = query.getResultList();

    books.forEach(b -> System.out.println(b.getTitle()));

    session.close(); 

5. Fetch Join (One-to-Many: Author and Books)

Retrieve authors and their associated books in a single query, avoiding the N+1 select problem.

 import jakarta.persistence.criteria.*;
    import org.hibernate.Session;
    import org.hibernate.SessionFactory;

    // ... (SessionFactory creation and session opening omitted for brevity)

    Session session = sessionFactory.openSession();
    CriteriaBuilder cb = session.getCriteriaBuilder();
    CriteriaQuery<Author> cq = cb.createQuery(Author.class);
    Root<Author> authorRoot = cq.from(Author.class);

    // Fetch join to eagerly load books
    authorRoot.fetch("books", JoinType.LEFT);  // Or JoinType.INNER for mandatory books

    cq.select(authorRoot);

    TypedQuery<Author> query = session.createQuery(cq);
    List<Author> authors = query.getResultList();

    for (Author author : authors) {
        System.out.println("Author: " + author.getName());
        author.getBooks().forEach(book -> System.out.println("  Book: " + book.getTitle()));
    }
    session.close(); 

Important Considerations

  • Join Types: Choose the appropriate join type based on your data retrieval requirements.
  • Association Names: Ensure you use the correct association names (field names in your entity classes) when defining joins.
  • Fetch Joins and Performance: Fetch joins are generally recommended for loading related data eagerly, improving performance by reducing the number of database queries.
  • Criteria API Version: The code examples use the JPA Criteria API, which is available in Hibernate. Ensure you have the correct dependencies (e.g., `jakarta.persistence` or `javax.persistence` depending on your Hibernate version) and imports.
  • Implicit vs. Explicit Joins: While you *can* sometimes write Criteria queries with implicit joins (referencing related entities within predicates without explicitly using `join()`), explicit joins are generally preferred for clarity and control, especially for more complex queries.

These examples provide a starting point for using joins in Hibernate Criteria queries. Adapt them to fit your specific entity relationships and data retrieval needs.