Enterprise Java

Spring Boot & Hibernate @PartitionKey Guide

In high-scale applications, partitioning your database tables can significantly improve performance and scalability. Hibernate provides support for partitioning with the @PartitionKey annotation, which helps you define logical partitions in your entities, making queries more efficient. In our  Spring Boot & Hibernate @PartitionKey guide we will explore how to use @PartitionKey in a Spring Boot application with PostgreSQL. We will see how partitioning entities can optimize database queries and improve performance in distributed setups.

1. Introduction to @PartitionKey

The @PartitionKey annotation is used in Hibernate (and other JPA-compatible frameworks) to indicate the field that should be used to partition a table or entity. Partitioning is particularly useful in distributed databases or sharded setups, where large datasets are divided across multiple nodes for improved performance and scalability. By specifying a partition key, you allow the database to efficiently locate and access only the relevant subset of data, rather than scanning the entire table. This can significantly reduce query times and improve overall system throughput.

1.1 Key Benefits

  • Improved Query Performance: Only the partition containing the required data is queried.
  • Scalability: Makes it easier to scale horizontally across multiple database nodes.
  • Efficient Data Management: Helps in organizing large datasets logically across partitions.

1.2 Setting Up PostgreSQL with Docker

We will use Docker to run PostgreSQL for our Spring Boot application. This simplifies the setup and allows easy cleanup.

docker run --name springboot-postgres \
    -e POSTGRES_USER=admin \
    -e POSTGRES_PASSWORD=admin123 \
    -e POSTGRES_DB=demo_db \
    -p 5432:5432 \
    -d postgres:15

Once the container is running, you can connect to it using your favorite SQL client with the credentials above.

2. Code Example

Below is a detailed Spring Boot example demonstrating @PartitionKey usage.

2.1 Maven Dependencies

To use Spring Data JPA with PostgreSQL, you need to include the following Maven dependencies in your pom.xml file:

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

The first dependency, spring-boot-starter-data-jpa, provides all the necessary libraries to work with JPA and Hibernate in a Spring Boot application, including entity management, repository support, and transaction management. The second dependency, postgresql, is the runtime driver for PostgreSQL, which allows your application to connect to a PostgreSQL database and perform CRUD operations seamlessly.

2.2 Application Properties

Configure your Spring Boot application to connect to a PostgreSQL database by adding the following properties in application.properties:

spring.datasource.url=jdbc:postgresql://localhost:5432/demo_db
spring.datasource.username=admin
spring.datasource.password=admin123
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

In this configuration, spring.datasource.url specifies the JDBC URL for the PostgreSQL database, while spring.datasource.username and spring.datasource.password provide the database credentials. The property spring.jpa.hibernate.ddl-auto=update ensures Hibernate automatically updates the database schema to match your entity definitions. spring.jpa.show-sql=true enables logging of all SQL queries, and spring.jpa.properties.hibernate.format_sql=true formats those SQL statements for better readability in the logs.

2.3 Entity with @PartitionKey

This example demonstrates how to define a JPA entity with a @PartitionKey annotation to enable partitioning by a specific field.

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import org.hibernate.annotations.PartitionKey;

@Entity
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @PartitionKey
    private String customerId;

    private String product;

    private int quantity;

    // Getters and Setters
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }

    public String getCustomerId() { return customerId; }
    public void setCustomerId(String customerId) { this.customerId = customerId; }

    public String getProduct() { return product; }
    public void setProduct(String product) { this.product = product; }

    public int getQuantity() { return quantity; }
    public void setQuantity(int quantity) { this.quantity = quantity; }
}

In this entity class, Order represents a database table with fields id, customerId, product, and quantity. The @Id annotation marks id as the primary key with auto-generated values. The @PartitionKey annotation on customerId indicates that this field will be used to partition the data, meaning all orders from the same customer will be grouped in the same partition for faster queries. The other fields store product details and quantity, with standard getters and setters provided for JPA operations.

2.4 Repository

The repository interface provides methods to perform CRUD operations on the Order entity and custom queries based on the partition key.

import org.springframework.data.jpa.repository.JpaRepository;

public interface OrderRepository extends JpaRepository<Order, Long> {
    List<Order> findByCustomerId(String customerId);
}

The OrderRepository extends JpaRepository, which provides built-in CRUD operations such as save, findById, delete, and more. Additionally, the custom method findByCustomerId allows querying all orders for a specific customer. Since customerId is annotated with @PartitionKey in the entity, queries using this field are optimized for partitioned storage, improving performance when retrieving data for a particular customer.

2.5 Service

The service layer contains business logic for managing Order entities and interacts with the repository to handle data operations.

import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class OrderService {
    private final OrderRepository orderRepository;

    public OrderService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    public List<Order> getOrdersByCustomer(String customerId) {
        return orderRepository.findByCustomerId(customerId);
    }

    public Order createOrder(Order order) {
        return orderRepository.save(order);
    }
}

The OrderService class is annotated with @Service to indicate it is a Spring service component. It uses OrderRepository to perform database operations. The getOrdersByCustomer method retrieves all orders for a given customerId, leveraging the partition key for efficient queries. The createOrder method saves a new order to the database. By centralizing business logic in the service layer, you separate concerns and make your application easier to maintain and test.

2.6 Controller

The controller layer exposes REST APIs to interact with Order entities, allowing clients to retrieve and create orders via HTTP requests.

import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/orders")
public class OrderController {

    private final OrderService orderService;

    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    @GetMapping("/customer/{customerId}")
    public List<Order> getOrders(@PathVariable String customerId) {
        return orderService.getOrdersByCustomer(customerId);
    }

    @PostMapping
    public Order createOrder(@RequestBody Order order) {
        return orderService.createOrder(order);
    }
}

The OrderController class is annotated with @RestController, making it a Spring REST controller, and @RequestMapping("/orders") sets the base URL for all endpoints. The getOrders endpoint handles GET requests for a specific customerId and returns all orders associated with that customer by calling the service layer. The createOrder endpoint handles POST requests to add a new order, accepting the order data in the request body. This setup provides a clean separation between API, service, and repository layers, while leveraging @PartitionKey for optimized data access.

2.7 Output Example

Once the Spring Boot application is running (Run command: mvn spring-boot:run), you can use the API endpoints to create and retrieve orders. The following example demonstrates typical POST and GET requests:

curl -X POST http://localhost:8080/orders \
     -H "Content-Type: application/json" \
     -d '{
           "customerId": "C123",
           "product": "Laptop",
           "quantity": 2
         }'

curl http://localhost:8080/orders/customer/C123

[
    {
        "id": 1,
        "customerId": "C123",
        "product": "Laptop",
        "quantity": 2
    }
]

In this example, a new order for customer C123 is created using a POST request with order details in the request body. A subsequent GET request to /orders/customer/C123 retrieves all orders for that customer, showing the order with its automatically generated id, along with the customerId, product<

3. Conclusion

Using @PartitionKey in Hibernate helps in logically partitioning your entities for better performance and scalability. With Spring Boot and PostgreSQL, this setup allows you to write efficient queries while maintaining a clean and maintainable codebase. The approach is especially useful in high-volume applications where data is distributed across partitions for faster access.

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
Back to top button