You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
π― 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
{
"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.```bashcurl -X POST http://localhost:8080/api/v1/threat-actors/search-sliced \-H "Content-Type: application/json" \-d '{ ... same body as /search ... }'