Baeldung

Java, Spring and Web Development tutorials

 

PartitionKey in Hibernate: A Practical Guide for Spring Boot
2025-09-12 01:28 UTC by Sachin Verma

1. Overview

Working with large datasets in relational databases can challenge query performance. While Hibernate provides optimizations for entity mapping, partitioning becomes essential as the dataset expands.

In this tutorial, we’re going to dive into how we can use Spring Boot to work with partitioned tables. We’ll be using Hibernate and Spring Data JPA to handle the interactions, but we’ll see that the actual partitioning is something we configure directly within the database itself, like PostgreSQL.

2. The Role of @PartitionKey

When we split a really big table into smaller, more manageable tables, it’s called partitioning. Each partition contains a subset of data, often determined by a partition key. In Hibernate, we can map a specific column as the partition key so that queries can target only the relevant partitions, drastically improving query speed.

Partitioning creates separate physical tables for each data segment. For example, Sales table partitioned by saleDate, we might have sales_2024_q1, sales_2024_q2, and so on. This physical separation is what enables the performance gains.

We cannot simply partition an existing, large table. The process requires a migration strategy. This usually involves creating a new partitioned table structure and then migrating the data from the old table into the new one. This ensures data integrity and consistency.

The full benefit of partitioning is achieved when our queries include the partition key in their WHERE clauses. If a query doesn’t specify the partition key, the database’s query planner will likely have to search all partitions, which negates the performance benefits.

3. Setting Up PostgreSQL Database

We’ll be using PostgreSQL as our DB. Let’s create the table with a PARTITION BY clause, which in PostgreSQL, we might define as:

CREATE TABLE sales (
    id BIGINT PRIMARY KEY,
    sale_date DATE NOT NULL,
    amount DECIMAL(10, 2)
) PARTITION BY RANGE (sale_date);
CREATE TABLE sales_2024_q1 PARTITION OF sales
FOR VALUES FROM ('2024-01-01') TO ('2024-04-01');
CREATE TABLE sales_2024_q2 PARTITION OF sales
FOR VALUES FROM ('2024-04-01') TO ('2024-07-01');
-- And so on for other partitions

4. Dependencies Required

We’d require spring-boot-starter-web to create our REST endpoints:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Our primary dependency would be spring-boot-starter-data-jpa for database interactions. It bundles Spring Data JPA and Hibernate, the default JPA provider.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

This is the JDBC (Java Database Connectivity) driver for PostgreSQL. The JDBC driver is a crucial software component that enables our Java application to connect to and interact with a PostgreSQL database:

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <scope>runtime</scope>
</dependency>

5. Setting Up a Spring Boot Project

Here’s a clear example of how to represent a partition key in a Spring Boot and Hibernate setup.

5.1. Database Entity Setup

First, let’s look at the entity. We’ll create a Sales entity and annotate the saleDate field with @PartitionKey to enable Hibernate’s optimizations:

@Entity
@Table(name = "sales")
public class Sales {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @PartitionKey
    private LocalDate saleDate;
    private BigDecimal amount;
    public Sales(Long id, LocalDate saleDate, BigDecimal amount) {
        this.id = id;
        this.saleDate = saleDate;
        this.amount = amount;
    }
    public Sales() {
    }
}

@PartitionKey is a custom annotation that signals Hibernate that this column will be the basis for table partitioning in the database. This annotation is only present in Hibernate 6.2 and later.

Next, we define our repository, which extends JpaRepository to leverage Spring Data JPA’s power for data access:

@Repository
public interface SalesRepository extends JpaRepository<Sales, Long> {
}

5.2. Design Controller to Test Partition

Now, let’s create a controller to interact with our Sales data. The testPartition() and getAllPartition() methods demonstrate saving a new Sales record. We need to consider that with a partitioned table, the database will automatically place this record into the correct partition based on the saleDate:

@RestController
public class Controller {
    @Autowired
    SalesRepository salesRepository;
    @GetMapping
    public ResponseEntity<List<Sales>> getAllPartition() {
        return ResponseEntity.ok()
          .body(salesRepository.findAll());
    }
    @GetMapping("add")
    public ResponseEntity testPartition() {
        return ResponseEntity.ok()
          .body(salesRepository.save(new Sales(104L, LocalDate.of(2025, 02, 01),
            BigDecimal.valueOf(Double.parseDouble("8476.34d")))));
    }
}

Finally, we’ve the application.properties file, where we configure our database connection. We also have to update the username and password as per the DB:

spring.application.name=partitionKeyDemo
# PostgreSQL connection properties
spring.datasource.url=jdbc:postgresql://localhost:5432/salesTest
spring.datasource.username=username
spring.datasource.password=password

5.3. Output

Now, when we run our application and hit http://localhost:8080/add , we can see the row going in the proper partition in PostgreSQL DB:

                   

Here we can see two partitions created on the left pane. Then, we can run a query to fetch the row inserted in the correct partition automatically:

SELECT * FROM public.sales_2025_q1;

6. Optimizing Queries With Partitioning

To truly benefit from partitioning, we must design our queries carefully. Let’s consider a scenario where we want to find all sales for a specific date. A well-designed query would look something like this:

SELECT * FROM sales WHERE sale_date = '2025-02-01';

In this case, the database can use the partition key (sale_date) to immediately identify and search only the partition corresponding to ‘2025-02-01’, completely ignoring all other partitions. This is a highly efficient operation.
However, if we run a query that does not include the partition key, such as:

SELECT * FROM sales WHERE amount > 5000;

The database will be forced to scan every single partition to find the matching records. This type of query is known as a global scan, and it’ll likely perform much worse than a query on a non-partitioned, properly indexed table.

7. Conclusion

In this tutorial, we looked at how combining Hibernate’s entity mapping with database-level partitioning we can keep performance consistent as data grows. Partitioning is a powerful technique for scaling our relational databases to handle massive datasets.

While the implementation resides at the database level, Hibernate and Spring Boot provide the necessary tools to interact with these structures seamlessly. At the same time, it is necessary to build queries involving the partition column to use the benefits of the partitioned key.

As always, the code for this article is available over on GitHub.

The post PartitionKey in Hibernate: A Practical Guide for Spring Boot first appeared on Baeldung.
       

 

Content mobilized by FeedBlitz RSS Services, the premium FeedBurner alternative.