CRUD Operations with Hibernate (Create, Read, Update, Delete)

This is a hands-on lesson demonstrating how to perform basic CRUD operations using Hibernate. You'll learn how to save new objects to the database (Create), retrieve objects (Read), modify existing objects (Update), and delete objects (Delete) using Hibernate's API.


Hibernate Criteria API

What is the Criteria API?

The Criteria API is a programmatic way to define database queries in Java using Hibernate. Instead of writing HQL (Hibernate Query Language) or native SQL strings, you construct queries using Java objects and methods. This offers several advantages:

  • Type Safety: Queries are constructed using Java types, reducing the risk of runtime errors due to syntax issues in query strings.
  • Compile-time Checking: Some errors can be detected at compile time, making development more robust.
  • Dynamic Query Construction: You can build queries dynamically based on runtime conditions, adding or removing criteria as needed.
  • Abstraction from Database-Specific Dialect: Hibernate translates the Criteria query into the appropriate SQL dialect for your database, making your code more portable.

Using the Criteria API for Programmatic Query Construction

The Criteria API revolves around the CriteriaBuilder and CriteriaQuery interfaces. Here's a breakdown of how it works:

  1. Obtain a CriteriaBuilder: This object is the factory for creating query elements like predicates (WHERE clause conditions), ordering rules, and selection expressions. You get it from the EntityManager or SessionFactory.
  2. Create a CriteriaQuery: This object represents the query itself. You specify the result type (the entity class you're querying) when creating it.
  3. Define the Root Entity: You need to specify the root entity for the query. This is the entity class that you're querying against.
  4. Add Predicates (WHERE Clause): Use the CriteriaBuilder to create predicates for filtering the results. These predicates are added to the CriteriaQuery.
  5. Specify Ordering (ORDER BY Clause): Use the CriteriaBuilder to define ordering rules for the results. These are added to the CriteriaQuery.
  6. Create a TypedQuery: Finally, you create a TypedQuery from the CriteriaQuery using the EntityManager.
  7. Execute the Query: Execute the TypedQuery to retrieve the results.

Building Queries using CriteriaBuilder and CriteriaQuery

Let's illustrate with an example. Assume you have an entity class called Employee with properties like id, name, and salary.

 import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;
import java.util.List;

// Assume you have an Employee entity defined, e.g.
// @Entity
// public class Employee { ... }

public class CriteriaExample {

    public static void main(String[] args) {

        EntityManagerFactory emf = Persistence.createEntityManagerFactory("YourPersistenceUnitName"); // Replace with your persistence unit
        EntityManager em = emf.createEntityManager();

        try {
            // 1. Obtain a CriteriaBuilder
            CriteriaBuilder cb = em.getCriteriaBuilder();

            // 2. Create a CriteriaQuery for the Employee entity
            CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);

            // 3. Define the root entity
            Root<Employee> root = cq.from(Employee.class);

            // 4. Add a predicate (WHERE clause):  Salary greater than 50000
            cq.where(cb.gt(root.get("salary"), 50000.0));  // Assuming salary is a Double

            // 5. Specify ordering (ORDER BY clause): Order by name ascending
            cq.orderBy(cb.asc(root.get("name")));

            //  6. Create a TypedQuery
            TypedQuery<Employee> query = em.createQuery(cq);

            // 7. Execute the query
            List<Employee> employees = query.getResultList();

            // Process the results
            for (Employee employee : employees) {
                System.out.println(employee.getName() + ": " + employee.getSalary());
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            em.close();
            emf.close();
        }
    }
} 

Explanation of the Code:

  • EntityManagerFactory emf = Persistence.createEntityManagerFactory("YourPersistenceUnitName");: Creates an `EntityManagerFactory` for your persistence unit. **Replace `"YourPersistenceUnitName"` with the actual name defined in your `persistence.xml` file.**
  • EntityManager em = emf.createEntityManager();: Creates an `EntityManager` instance for interacting with the database.
  • CriteriaBuilder cb = em.getCriteriaBuilder();: Gets the `CriteriaBuilder` from the `EntityManager`.
  • CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);: Creates a `CriteriaQuery` for the `Employee` entity. This specifies that the query will return a list of `Employee` objects.
  • Root<Employee> root = cq.from(Employee.class);: Defines the root entity (`Employee`) for the query. The `Root` object represents the entity being queried.
  • cq.where(cb.gt(root.get("salary"), 50000.0));: Adds a predicate (WHERE clause) to the query. This predicate filters the results to only include employees whose salary is greater than 50000. cb.gt() creates a "greater than" predicate. root.get("salary") accesses the "salary" property of the `Employee` entity. **Make sure the type of 50000.0 matches the type of salary in your Employee entity (e.g., use 50000 if salary is an Integer).**
  • cq.orderBy(cb.asc(root.get("name")));: Adds an ordering rule (ORDER BY clause) to the query. This orders the results by the "name" property of the `Employee` entity in ascending order. cb.asc() specifies ascending order.
  • TypedQuery<Employee> query = em.createQuery(cq);: Creates a `TypedQuery` from the `CriteriaQuery`. The `TypedQuery` is a type-safe query that can be executed.
  • List<Employee> employees = query.getResultList();: Executes the query and retrieves the results as a list of `Employee` objects.
  • The loop then iterates through the `employees` list and prints the name and salary of each employee.
  • Finally, the `EntityManager` and `EntityManagerFactory` are closed to release resources.

Important Considerations:

  • Persistence Unit Name: Ensure that "YourPersistenceUnitName" in the code is replaced with the correct name of your persistence unit defined in your `persistence.xml` file. This is crucial for connecting to the database.
  • Entity Mapping: The Employee entity must be correctly mapped to a database table using JPA annotations (e.g., @Entity, @Table, @Id, @Column).
  • Dependencies: Make sure you have the necessary Hibernate and JPA dependencies in your project. These are usually managed through Maven or Gradle.
  • Property Types: The type of the literals used in predicates (e.g., 50000.0) must match the type of the corresponding entity properties (e.g., salary).

Common CriteriaBuilder Methods

Here's a list of some common methods from the CriteriaBuilder interface used for constructing queries:

  • equal(Expression<?> x, Object y): Creates an equality predicate (x = y).
  • notEqual(Expression<?> x, Object y): Creates an inequality predicate (x != y).
  • gt(Expression<? extends Number> x, Number y): Creates a "greater than" predicate (x > y).
  • ge(Expression<? extends Number> x, Number y): Creates a "greater than or equal to" predicate (x >= y).
  • lt(Expression<? extends Number> x, Number y): Creates a "less than" predicate (x < y).
  • le(Expression<? extends Number> x, Number y): Creates a "less than or equal to" predicate (x <= y).
  • like(Expression<String> x, String pattern): Creates a "like" predicate (x LIKE pattern).
  • between(Expression<?> v, Object x, Object y): Creates a "between" predicate (v BETWEEN x AND y).
  • isNull(Expression<?> x): Creates a "is null" predicate (x IS NULL).
  • isNotNull(Expression<?> x): Creates a "is not null" predicate (x IS NOT NULL).
  • and(Predicate... restrictions): Creates a conjunction of predicates (restriction1 AND restriction2 AND ...).
  • or(Predicate... restrictions): Creates a disjunction of predicates (restriction1 OR restriction2 OR ...).
  • not(Predicate restriction): Creates a negation of a predicate (NOT restriction).
  • asc(Expression<?> x): Creates an ascending ordering.
  • desc(Expression<?> x): Creates a descending ordering.
  • count(Expression<?> x): Creates an expression for counting.
  • sum(Expression<? extends Number> x): Creates an expression for summing numbers.
  • avg(Expression<? extends Number> x): Creates an expression for calculating the average.
  • min(Expression<? extends Number> x): Creates an expression for finding the minimum value.
  • max(Expression<? extends Number> x): Creates an expression for finding the maximum value.