Skip to content

prasadgaikwad/spring-data-jpa-specification

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

1 Commit
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Spring Data JPA Specification - Generic Query Builder

A powerful, reusable Spring Data JPA Specification framework that enables dynamic querying on any entity with filtering, sorting, and pagination. Includes a complete STIX 2.1 ThreatActor implementation as a reference.

Spring Boot Java License


🎯 Overview

This project provides a generic, type-safe query builder that eliminates the need to write custom repository methods for each search scenario. Simply extend SpecificationRepository and use SearchRequest to dynamically filter any entity.

Key Features

  • βœ… 14 filter operators (EQUALS, CONTAINS, IN, BETWEEN, etc.)
  • βœ… Type-safe generic implementation
  • βœ… Zero boilerplate - no custom repository methods needed
  • βœ… Nested property support (e.g., address.city)
  • βœ… Automatic type conversion for dates, numbers, enums
  • βœ… Pagination & multi-column sorting
  • βœ… OpenAPI/Swagger documentation

πŸ—οΈ Architecture

High-Level Component Diagram

graph TB
    subgraph "Client Layer"
        C[REST Client]
    end
    
    subgraph "Controller Layer"
        CTRL[ThreatActorController]
    end
    
    subgraph "Service Layer"
        SVC[ThreatActorService]
    end
    
    subgraph "Generic Specification Framework"
        GSB[GenericSpecificationBuilder]
        GS[GenericSpecification]
        SR[SearchRequest]
        SC[SearchCriteria]
        FO[FilterOperator]
    end
    
    subgraph "Repository Layer"
        REPO[ThreatActorRepository]
        SPEC_REPO[SpecificationRepository]
        JPA[JpaSpecificationExecutor]
    end
    
    subgraph "Data Layer"
        DB[(H2 Database)]
    end
    
    C -->|SearchRequest JSON| CTRL
    CTRL --> SVC
    SVC --> GSB
    GSB --> GS
    SR --> GSB
    SC --> GS
    FO --> GS
    GSB -->|Specification + Pageable| REPO
    REPO --> SPEC_REPO
    SPEC_REPO --> JPA
    JPA --> DB
Loading

Specification Building Flow

sequenceDiagram
    participant Client
    participant Controller
    participant Service
    participant SpecBuilder as GenericSpecificationBuilder
    participant Spec as GenericSpecification
    participant Repo as Repository
    participant DB as Database
    
    Client->>Controller: POST /search (SearchRequest)
    Controller->>Service: search(request)
    
    Service->>SpecBuilder: buildFromRequest(request)
    
    loop For each SearchCriteria
        SpecBuilder->>Spec: new GenericSpecification(criteria)
        Spec-->>SpecBuilder: Specification<T>
    end
    
    SpecBuilder-->>Service: Combined Specification (AND)
    
    Service->>SpecBuilder: buildPageable(request)
    SpecBuilder-->>Service: Pageable with Sort
    
    Service->>Repo: findAll(spec, pageable)
    Repo->>DB: Dynamic SQL Query
    DB-->>Repo: ResultSet
    Repo-->>Service: Page<Entity>
    Service-->>Controller: Page<Entity>
    Controller-->>Client: PagedResponse JSON
Loading

Class Diagram

classDiagram
    class SpecificationRepository~T, ID~ {
        <<interface>>
    }
    
    class JpaRepository~T, ID~ {
        <<interface>>
    }
    
    class JpaSpecificationExecutor~T~ {
        <<interface>>
        +findAll(Specification~T~ spec) List~T~
        +findAll(Specification~T~ spec, Pageable pageable) Page~T~
    }
    
    class ThreatActorRepository {
        <<interface>>
    }
    
    class GenericSpecification~T~ {
        -SearchCriteria criteria
        +toPredicate(Root, CriteriaQuery, CriteriaBuilder) Predicate
    }
    
    class GenericSpecificationBuilder~T~ {
        -List~SearchCriteria~ criteriaList
        +with(SearchCriteria) GenericSpecificationBuilder
        +with(List~SearchCriteria~) GenericSpecificationBuilder
        +build() Specification~T~
        +buildFromRequest(SearchRequest)$ Specification~T~
        +buildPageable(SearchRequest)$ Pageable
    }
    
    class SearchRequest {
        +List~SearchCriteria~ criteria
        +List~SortCriteria~ sortBy
        +int page
        +int size
    }
    
    class SearchCriteria {
        +String field
        +FilterOperator operator
        +Object value
        +Object valueTo
    }
    
    class FilterOperator {
        <<enumeration>>
        EQUALS
        NOT_EQUALS
        CONTAINS
        STARTS_WITH
        ENDS_WITH
        GREATER_THAN
        LESS_THAN
        IN
        BETWEEN
        IS_NULL
        ...
    }
    
    SpecificationRepository --|> JpaRepository
    SpecificationRepository --|> JpaSpecificationExecutor
    ThreatActorRepository --|> SpecificationRepository
    GenericSpecificationBuilder ..> GenericSpecification : creates
    GenericSpecificationBuilder ..> SearchRequest : uses
    GenericSpecification ..> SearchCriteria : uses
    SearchCriteria --> FilterOperator
    SearchRequest *-- SearchCriteria
Loading

πŸš€ Quick Start

Prerequisites

  • Java 21+
  • Maven 3.9+

Run the Application

git clone <repository-url>
cd spring-data-jpa-specification
./mvnw spring-boot:run

Access Points

Endpoint URL
Swagger UI http://localhost:8080/swagger-ui.html
API Docs http://localhost:8080/api-docs
H2 Console http://localhost:8080/h2-console
Actuator http://localhost:8080/actuator

πŸ“¦ Using the Generic Query Builder

Step 1: Extend SpecificationRepository

public interface YourEntityRepository 
    extends SpecificationRepository<YourEntity, Long> {
}

Step 2: Use in Service

@Service
public class YourEntityService {
    
    private final YourEntityRepository repository;
    
    public Page<YourEntity> search(SearchRequest request) {
        Specification<YourEntity> spec = GenericSpecificationBuilder.buildFromRequest(request);
        Pageable pageable = GenericSpecificationBuilder.buildPageable(request);
        return repository.findAll(spec, pageable);
    }
}

Step 3: Send Search Request

{
  "criteria": [
    {"field": "name", "operator": "CONTAINS", "value": "test"},
    {"field": "status", "operator": "IN", "value": ["ACTIVE", "PENDING"]},
    {"field": "createdAt", "operator": "BETWEEN", "value": "2024-01-01", "valueTo": "2024-12-31"}
  ],
  "sortBy": [
    {"field": "name", "direction": "ASC"},
    {"field": "createdAt", "direction": "DESC"}
  ],
  "page": 0,
  "size": 20
}

πŸ”§ Filter Operators

graph LR
    subgraph "Text Operators"
        EQ[EQUALS]
        NEQ[NOT_EQUALS]
        CON[CONTAINS]
        SW[STARTS_WITH]
        EW[ENDS_WITH]
    end
    
    subgraph "Comparison Operators"
        GT[GREATER_THAN]
        LT[LESS_THAN]
        GTE[GREATER_THAN_OR_EQUAL]
        LTE[LESS_THAN_OR_EQUAL]
        BET[BETWEEN]
    end
    
    subgraph "Collection Operators"
        IN_OP[IN]
        NIN[NOT_IN]
    end
    
    subgraph "Null Operators"
        NULL[IS_NULL]
        NNULL[IS_NOT_NULL]
    end
Loading
Operator SQL Equivalent Example Value
EQUALS = value "APT29"
NOT_EQUALS != value "inactive"
CONTAINS LIKE %value% "threat" (case-insensitive)
STARTS_WITH LIKE value% "APT"
ENDS_WITH LIKE %value "Group"
GREATER_THAN > value "2024-01-01"
LESS_THAN < value 100
GREATER_THAN_OR_EQUAL >= value 50
LESS_THAN_OR_EQUAL <= value "2024-12-31"
IN IN (values) ["active", "pending"]
NOT_IN NOT IN (values) ["deleted", "archived"]
IS_NULL IS NULL (no value needed)
IS_NOT_NULL IS NOT NULL (no value needed)
BETWEEN BETWEEN x AND y value: "2024-01-01", valueTo: "2024-12-31"

πŸ“‘ REST API Endpoints

ThreatActor CRUD + Search

Method Endpoint Description
POST /api/v1/threat-actors Create new ThreatActor
GET /api/v1/threat-actors/{id} Get by ID
PUT /api/v1/threat-actors/{id} Update existing
DELETE /api/v1/threat-actors/{id} Delete
POST /api/v1/threat-actors/search Dynamic search

Example: Create ThreatActor

curl -X POST http://localhost:8080/api/v1/threat-actors \
  -H "Content-Type: application/json" \
  -d '{
    "name": "APT29",
    "description": "Russian threat actor group",
    "sophistication": "expert",
    "resourceLevel": "government",
    "primaryMotivation": "espionage",
    "threatActorTypes": ["nation-state", "spy"],
    "aliases": ["Cozy Bear", "The Dukes"]
  }'

Example: Search with Filters

curl -X POST http://localhost:8080/api/v1/threat-actors/search \
  -H "Content-Type: application/json" \
  -d '{
    "criteria": [
      {"field": "name", "operator": "CONTAINS", "value": "APT"},
      {"field": "sophistication", "operator": "IN", "value": ["advanced", "expert"]}
    ],
    "sortBy": [{"field": "name", "direction": "ASC"}],
    "page": 0,
    "size": 10
  }'

Response Format

{
  "content": [...],
  "page": 0,
  "size": 10,
  "totalElements": 25,
  "totalPages": 3,
  "first": true,
  "last": false
}

### Search with Slice (No Count Query)

Use `/search-sliced` to improve performance by avoiding the total count query. Ideal for infinite scroll or "load more" features.

```bash
curl -X POST http://localhost:8080/api/v1/threat-actors/search-sliced \
  -H "Content-Type: application/json" \
  -d '{ ... same body as /search ... }'

SlicedResponse Format

{
  "content": [...],
  "page": 0,
  "size": 10,
  "first": true,
  "last": false,
  "hasNext": true
}

---

## πŸ§ͺ Testing

```bash
# Run all tests
./mvnw test

# Run with coverage
./mvnw test jacoco:report

Test Coverage

  • Unit Tests: GenericSpecificationBuilderTest - Tests builder logic
  • Integration Tests: ThreatActorRepositoryIntegrationTest - Tests all filter operators against H2

πŸ—‚οΈ Project Structure

src/main/java/dev/prasadgaikwad/specification/
β”œβ”€β”€ Application.java
β”œβ”€β”€ core/                          # ⭐ Generic Framework (reusable)
β”‚   β”œβ”€β”€ FilterOperator.java
β”‚   β”œβ”€β”€ SearchCriteria.java
β”‚   β”œβ”€β”€ SortCriteria.java
β”‚   β”œβ”€β”€ SearchRequest.java
β”‚   β”œβ”€β”€ GenericSpecification.java
β”‚   └── GenericSpecificationBuilder.java
β”œβ”€β”€ repository/
β”‚   β”œβ”€β”€ SpecificationRepository.java  # ⭐ Base interface
β”‚   β”œβ”€β”€ SliceSpecificationExecutor.java # ⭐ Slice support
β”‚   └── ThreatActorRepository.java
β”œβ”€β”€ entity/
β”‚   └── ThreatActor.java              # STIX 2.1 entity
β”œβ”€β”€ dto/
β”‚   β”œβ”€β”€ ThreatActorDTO.java
β”‚   β”œβ”€β”€ PagedResponse.java
β”‚   └── SlicedResponse.java

β”œβ”€β”€ service/
β”‚   └── ThreatActorService.java
β”œβ”€β”€ controller/
β”‚   └── ThreatActorController.java
└── exception/
    └── GlobalExceptionHandler.java

πŸ”— Technology Stack

Component Technology
Framework Spring Boot 3.3.7
Language Java 21
ORM Spring Data JPA / Hibernate 6.5
Database H2 (in-memory, can switch to PostgreSQL)
API Docs OpenAPI 3 / Swagger UI
Build Maven

πŸ“„ License

MIT License - feel free to use this in your projects!

About

Supercharge Spring Data JPA: Dynamic Filtering & Performance Optimization with Slices

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages