Core Java

Hibernate Group-By Criteria API Example

In modern Java applications, managing data through Hibernate is a common practice, especially when working with complex queries. One of the most powerful features Hibernate offers is the Criteria API, which provides a programmatic way to build queries dynamically. Among its many capabilities, the “Hibernate Group By Criteria API” allows developers to group data in a structured manner, simplifying complex reporting tasks. Let us delve into understanding how this functionality can be utilized to group entities efficiently based on specific attributes, like counting the number of products in each category, thereby optimizing the performance of data retrieval operations.

1. Overview

Hibernate is a popular ORM (Object-Relational Mapping) framework for Java, which simplifies database operations by mapping Java objects to database tables. One of the key features provided by Hibernate is the ability to use the Criteria API for building dynamic queries. The Group-By clause in SQL allows you to group rows that have the same values in specified columns into aggregated data, such as counts, sums, or averages. Hibernate’s Criteria API supports dynamic Group-By queries, providing flexibility to handle such requirements in Java applications.

2. Dependencies

Before starting, ensure that your project has the necessary dependencies to use Hibernate and JPA (Java Persistence API). For Maven users, the following dependencies should be added to the pom.xml file:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>your__jar__version</version>
</dependency>

<dependency>
    <groupId>javax.persistence</groupId>
    <artifactId>javax.persistence-api</artifactId>
    <version>your__jar__version</version>
</dependency>

<!-- H2 Database Dependency -->
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>your__jar__version</version>
</dependency>

Additionally, you’ll need a Hibernate configuration file, such as hibernate.cfg.xml, and the appropriate database connection details.

<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.dialect">org.hibernate.dialect.H2Dialect</property>
        <property name="hibernate.connection.driver_class">org.h2.Driver</property>
        <property name="hibernate.connection.url">jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1</property>
        <property name="hibernate.connection.username">sa</property>
        <property name="hibernate.connection.password"></property>
        <property name="hibernate.hbm2ddl.auto">update</property>
        <property name="hibernate.show_sql">true</property>
        <property name="hibernate.format_sql">true</property>
    </session-factory>
</hibernate-configuration>

2.1 Add data to the H2 database

Below is the SQL script to create the required table and insert sample data into the H2 database.

-- Create the Product table
CREATE TABLE Product (
    id INT PRIMARY KEY,
    name VARCHAR(255),
    category VARCHAR(255)
);

-- Insert sample data into the Product table

-- Electronics category (15 products)
INSERT INTO Product (id, name, category) VALUES (1, 'Laptop', 'Electronics');
INSERT INTO Product (id, name, category) VALUES (2, 'Smartphone', 'Electronics');
INSERT INTO Product (id, name, category) VALUES (3, 'Tablet', 'Electronics');
INSERT INTO Product (id, name, category) VALUES (4, 'Smartwatch', 'Electronics');
INSERT INTO Product (id, name, category) VALUES (5, 'Headphones', 'Electronics');
INSERT INTO Product (id, name, category) VALUES (6, 'Speaker', 'Electronics');
INSERT INTO Product (id, name, category) VALUES (7, 'Camera', 'Electronics');
INSERT INTO Product (id, name, category) VALUES (8, 'Drone', 'Electronics');
INSERT INTO Product (id, name, category) VALUES (9, 'Monitor', 'Electronics');
INSERT INTO Product (id, name, category) VALUES (10, 'Keyboard', 'Electronics');
INSERT INTO Product (id, name, category) VALUES (11, 'Mouse', 'Electronics');
INSERT INTO Product (id, name, category) VALUES (12, 'Router', 'Electronics');
INSERT INTO Product (id, name, category) VALUES (13, 'Projector', 'Electronics');
INSERT INTO Product (id, name, category) VALUES (14, 'External Hard Drive', 'Electronics');
INSERT INTO Product (id, name, category) VALUES (15, 'Smart TV', 'Electronics');

-- Furniture category (8 products)
INSERT INTO Product (id, name, category) VALUES (16, 'Chair', 'Furniture');
INSERT INTO Product (id, name, category) VALUES (17, 'Sofa', 'Furniture');
INSERT INTO Product (id, name, category) VALUES (18, 'Table', 'Furniture');
INSERT INTO Product (id, name, category) VALUES (19, 'Bookshelf', 'Furniture');
INSERT INTO Product (id, name, category) VALUES (20, 'Cabinet', 'Furniture');
INSERT INTO Product (id, name, category) VALUES (21, 'Desk', 'Furniture');
INSERT INTO Product (id, name, category) VALUES (22, 'Armchair', 'Furniture');
INSERT INTO Product (id, name, category) VALUES (23, 'Stool', 'Furniture');

-- Clothing category (12 products)
INSERT INTO Product (id, name, category) VALUES (24, 'Shirt', 'Clothing');
INSERT INTO Product (id, name, category) VALUES (25, 'Pants', 'Clothing');
INSERT INTO Product (id, name, category) VALUES (26, 'Jacket', 'Clothing');
INSERT INTO Product (id, name, category) VALUES (27, 'T-shirt', 'Clothing');
INSERT INTO Product (id, name, category) VALUES (28, 'Jeans', 'Clothing');
INSERT INTO Product (id, name, category) VALUES (29, 'Sweater', 'Clothing');
INSERT INTO Product (id, name, category) VALUES (30, 'Shorts', 'Clothing');
INSERT INTO Product (id, name, category) VALUES (31, 'Skirt', 'Clothing');
INSERT INTO Product (id, name, category) VALUES (32, 'Dress', 'Clothing');
INSERT INTO Product (id, name, category) VALUES (33, 'Blazer', 'Clothing');
INSERT INTO Product (id, name, category) VALUES (34, 'Hat', 'Clothing');
INSERT INTO Product (id, name, category) VALUES (35, 'Gloves', 'Clothing');

3. Grouping Products Using Criteria API

Let’s consider a scenario where we have a Product entity and we need to group products by their category and count how many products exist in each category. To do this, we will use Hibernate’s Criteria API.

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.query.criteria.CriteriaBuilder;
import org.hibernate.query.criteria.CriteriaQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import java.util.List;

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
class Product {

    @Id
    private int id;
    private String name;
    private String category;

    // Getters and setters
}

public class ProductService {

    public static void main(String[] args) {
        // Set up Hibernate SessionFactory and Session
        SessionFactory sessionFactory = new Configuration().configure("hibernate.cfg.xml").addAnnotatedClass(Product.class).buildSessionFactory();
        Session session = sessionFactory.openSession();

        try {
            // Begin a transaction
            session.beginTransaction();

            // Create CriteriaBuilder and CriteriaQuery
            CriteriaBuilder builder = session.getCriteriaBuilder();
            CriteriaQuery<Object[]> query = builder.createQuery(Object[].class);

            // Define the root of the query
            Root<Product> root = query.from(Product.class);

            // Group by product category and count the number of products in each category
            query.multiselect(root.get("category"), builder.count(root))
                 .groupBy(root.get("category"));

            // Execute the query
            List<Object[]> result = session.createQuery(query).getResultList();

            // Process and display results
            for (Object[] row : result) {
                String category = (String) row[0];
                Long count = (Long) row[1];
                System.out.println("Category: " + category + ", Count: " + count);
            }

            // Commit the transaction
            session.getTransaction().commit();
        } finally {
            // Close the session
            session.close();
            sessionFactory.close();
        }
    }
}

3.1 Code Explanation

The ProductService class demonstrates how to use Hibernate’s Criteria API to perform a group-by operation and count the number of products in each category. The process begins by setting up a SessionFactory using the Hibernate configuration file hibernate.cfg.xml and adding the Product entity class. The Session is then created from the session factory to interact with the database.

The code inside the try block begins by initiating a transaction with session.beginTransaction(). This is followed by the creation of a CriteriaBuilder object, which is used to build a CriteriaQuery for querying the database. The CriteriaQuery specifies that the result will be an array of objects (Object[]), where each element contains the category and the count of products in that category.

Next, the root of the query is defined using the Root object, which represents the Product entity. The query.multiselect() method is used to select two fields: the category and the count of products in each category, achieved by the builder.count(root) method. The groupBy method is then applied to group the results by the category field.

The query is executed using session.createQuery(query).getResultList(), which returns a list of object arrays containing the category and count. The results are then processed using a for loop, where each row is cast to a String (category) and Long (count). These values are printed out to the console in the format: Category: [category], Count: [count].

Finally, after processing the results, the transaction is committed with session.getTransaction().commit(), and the session is closed along with the session factory in the finally block, ensuring resources are released properly.

4. Unit Test

Unit testing Hibernate Criteria queries can be done using frameworks like JUnit. Here’s a simple test case to verify the functionality of the “Group-By” query:

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import org.hibernate.Session;

class ProductServiceTest {

    @Test
    void testGroupByCategory() {
        // Setup Hibernate session
        Session session = HibernateUtil.getSessionFactory().openSession();
        session.beginTransaction();
        
        // Create Criteria query
        CriteriaBuilder builder = session.getCriteriaBuilder();
        CriteriaQuery<Object[]> query = builder.createQuery(Object[].class);
        Root<Product> root = query.from(Product.class);
        query.multiselect(root.get("category"), builder.count(root)).groupBy(root.get("category"));
        
        // Execute query and get results
        List<Object[]> results = session.createQuery(query).getResultList();
        
        // Assertions to verify expected output
        assertTrue(results.size() > 0, "Expected non-empty result");
        
        // Process results for validation (e.g., checking expected category and counts)
        Object[] result = results.get(0);
        assertNotNull(result[0], "Category should not be null");
        assertTrue((Long) result[1] > 0, "Count should be greater than 0");

        session.getTransaction().commit();
        session.close();
    }
}

4.1 Code Explanation

The ProductServiceTest class is a unit test for verifying the functionality of the Hibernate Criteria API, specifically the “Group-By” operation. This test focuses on grouping products by their category and counting the number of products in each category using the Criteria API.

The test begins by setting up a Hibernate session with the HibernateUtil.getSessionFactory().openSession() method. It then begins a transaction with session.beginTransaction() to ensure that the operations are executed within a transactional context.

A CriteriaBuilder is created using the session.getCriteriaBuilder() method. This builder is used to create a CriteriaQuery that will return an array of objects (Object[]). The query is configured to select the category field from the Product entity and the count of products in each category. The groupBy method is used to group the results by the category field, and the multiselect method specifies that both the category and the count will be part of the result.

The query is executed using session.createQuery(query).getResultList(), which retrieves the results from the database. The results are returned as a list of object arrays, where each array contains a category and the corresponding product count.

Next, assertions are used to validate the results. The first assertion, assertTrue(results.size() > 0), checks that the results list is not empty, meaning that at least one category has been returned. The second assertion checks that the category value is not null (assertNotNull(result[0])), and the third assertion ensures that the count of products for each category is greater than zero (assertTrue((Long) result[1] > 0)).

Finally, the transaction is committed using session.getTransaction().commit(), and the session is closed to release resources.

4.2 Code Output

Assuming the database contains products and each product is assigned to a category, the following output will be displayed on the console:

Category: Electronics, Count: 15
Category: Furniture, Count: 8
Category: Clothing, Count: 12

5. Conclusion

The Hibernate Criteria API is a powerful tool for building dynamic and type-safe queries in Java applications. Using the Group-By feature with Criteria API allows developers to group results based on certain properties and apply aggregation functions such as count, sum, and avg. It is particularly useful in scenarios where you need to retrieve grouped data from your database in a clean, efficient, and maintainable manner. Additionally, unit testing ensures that your criteria queries behave as expected, providing confidence in the correctness of your code.

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button