Spring Data Specification Builder
Spring boot library that provides fluent API to define and compose specifications for data querying.
Table of contents
Quick start
Maven:
<dependency>
<groupId>com.kirekov</groupId>
<artifactId>spring-data-specification-builder</artifactId>
</dependency>
Gradle:
dependencies {
implementation 'com.kirekov:spring-data-specification-builder'
}
Status
Usage
Basic
Specification<Student> spec = FluentSpecificationBuilder
.combinedWithAnd()
.like("name", "%imo%")
.eq("age", 18)
.build();
List<Student> students = studentRepository.findAll(spec);
It is recommended to use hibernate-jpamodelgen in order to auto generate field names. The query would like this.
Specification<Student> spec = FluentSpecificationBuilder
.combinedWithAnd()
.like(Student_.NAME, "%imo%")
.eq(Student_.AGE, 18)
.build();
List<Student> students = studentRepository.findAll(spec);
By the way, the library supports type-safe links Attribute<Entity, ?>
. So, the query can be enhanced with type checking.
Specification<Student> spec = FluentSpecificationBuilder
.combinedWithAnd()
.like(Student_.name, "%imo%") // compiles only with Attribute<Student, ?>
.eq(Student_.age, 18) // same
.build();
List<Student> students = studentRepository.findAll(spec);
Advanced
Architecture
FluentSpecificationBuilder
is the entry point to the library and the only public implementation.
Complex queries
The defined conditions can be applied with either &&
or ||
logical expressions.
final var builderAnd = FluentSpecificaitonBuilder.<Student>combinedWithAnd();
final var builderOr = FluentSpecificaitonBuilder.<Student>combinedWithOr();
More than that, you can invert any condition with not()
. Note that not()
strictly requires condition. You can't build the specification with unclosed not()
.
final var spec = FluentSpecificationBuilder
.<Student>combinedWithAnd()
.like(Student_.name, "%a%")
.not().eq(Student_.age, 22)
.build();
If you have to provide complex condition that cannot be interpreted with the library, you can use specification()
method.
final var spec = FLuentSpecificationBuilder()
.<Student>combinedWithAnd()
.eq(Student_.age, 20)
.specification((root, query, criteriaBuilder) -> /* your custom specification */)
.build();
That is also means that you can combine the results of different builders.
final var spec = FluentSpecificationBuilder()
.<Student>combinedWithOr()
.specification(
FluentSpecificationBuilder()
.<Student>combinedWithOr()
/* conditions */
.build()
)
.specification(
FluentSpecificationBuilder()
.<Student>combinedWithAnd()
/* conditions */
.build()
)
.build();
If you need to specify a field in a child entity, you can use PathFunction
.
final var spec = FluentSpecificationBuilder
.<Student>combinedWithAnd()
.like(Student_.name, "%a%")
.not().eq(root -> root.get(Student_.university).get(University_.name), "MIT")
.buildDistinct();