Skip to content

0xOrigin/query-filter-builder

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

116 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Query Filter Builder

Maven Central javadoc Coverage Branches License

Query Filter Builder is a robust and versatile library designed to dynamically generate type-safe JPA queries from HTTP query parameters and request bodies in Spring Boot applications.

Table of Contents

Overview

Query Filter Builder simplifies the process of dynamically filtering and sorting data in Spring Boot applications by converting HTTP query parameters and JSON request bodies into type-safe JPA predicates. It eliminates the complexity of manually writing filtering logic while maintaining clean and maintainable code. By abstracting query construction, it enables you to deliver clean software in less time, accelerating development and reducing maintenance overhead.

Features

  • Automatic Conversion: Effortlessly transform query parameters and request bodies into JPA predicates.
  • Comprehensive Operators: Supports +18 filtering operators including comparison, collection, string matching, null checks, and range operators.
  • Type-Safe Validation: Ensures data integrity with robust parameter validation and type conversion.
  • Clean API Design: Provides an intuitive and developer-friendly API with fluent interfaces.
  • Spring Data Integration: Seamlessly works with Spring Data JPA and Hibernate.
  • Nested Property Filtering: Enables filtering across related entities with customizable field delimiters.
  • Extensibility: Highly customizable with custom filter functions, custom sort functions, and expression providers.
  • Field Aliases: Allows clients to use stable, concise, or more user-friendly names in API requests.
  • Flexible Configuration: Configurable field delimiters, sort parameters, and localization support.
  • Custom Filters & Sorting: Support for complex, custom filtering and sorting logic beyond standard operators.
  • Registry-Based Architecture: Extensible operator and field registries for maximum flexibility.
  • Comprehensive Testing: Extensive unit test coverage for all operators, data types, and edge cases.
  • Security-First Design: Input validation, SQL injection prevention, and secure type conversion.
  • Performance Optimized: Efficient query building with minimal overhead and smart predicate generation.
  • Request Body Support: Full support for JSON request body filtering and sorting alongside query parameters.

Requirements

  • Java: Version 17 or higher
  • Spring Boot: Version 3.1.0 or higher
  • Jakarta Persistence API: Version 3.1.0 or higher

Installation

To integrate Query Filter Builder into your project, add the following Maven dependency:

<dependency>
    <groupId>io.github.0xorigin</groupId>
    <artifactId>query-filter-builder</artifactId>
    <version>2.1.0</version>
</dependency>

Configuration

  • The default field delimiter for nested property paths is . (dot). You can change it by setting the property query-filter-builder.defaults.field-delimiter in your application configuration.
    • Note: If you change the field delimiter, make sure to use the same delimiter in your filter and sort context template definitions (e.g., when specifying nested fields like createdBy.firstName).
  • The default sort parameter name used for sorting via HTTP query parameters is sort. You can change it by setting the property query-filter-builder.query-param.defaults.sort-parameter in your application configuration.

Quick Start

  1. Inject QueryFilterBuilder<T>: Inject an instance of QueryFilterBuilder<T> into your class—typically a service layer class—where T is your entity class.
  2. Define a Template: Use FilterContext.buildTemplateForType and SortContext.buildTemplateForType to specify which fields are filterable/sortable and how.
  3. Build a Context: Use the template's newSourceBuilder() to create a SourceBuilder, then provide the input source (query or body) and call buildFilterContext() or buildSortContext().
  4. Build Specification: Use your injected QueryFilterBuilder instance to build a JPA Specification for filtering and sorting.
  5. Apply to Repository: Ensure your repository extends JpaSpecificationExecutor and pass the specification to its findAll method.

Usage

Injecting QueryFilterBuilder

First, inject an instance of QueryFilterBuilder<T> into your desired class, typically a service. T should be your JPA entity.

import io.github._0xorigin.queryfilterbuilder.QueryFilterBuilder;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    private final QueryFilterBuilder<User> queryFilterBuilder;

    public UserService(QueryFilterBuilder<User> queryFilterBuilder) {
        this.queryFilterBuilder = queryFilterBuilder;
    }

    // ... your service logic
}

Defining Filter and Sort Templates

Define templates at application startup (e.g., in a private final field) to encapsulate allowed fields, operators, and custom logic for both filters and sorts. Use the builder pattern to configure templates for each entity.

// 1. Build and store the template (typically at startup in a private final field)
FilterContext.Template<User> userFilterTemplate = FilterContext.buildTemplateForType(User.class)
    .queryParam(configurer -> configurer
        .addFilter("role", Operator.EQ, Operator.NEQ, Operator.IN)
        .addFilter(Set.of("first-name", "name"), "firstName", Operator.EQ, Operator.CONTAINS, Operator.ICONTAINS) // with set of aliases
        .addFilter("lastName", Operator.EQ, Operator.CONTAINS, Operator.ICONTAINS)
        .addFilter("isActive", (root, cq, cb) -> root.get("isActive"), Operator.EQ) // Can provide expression
        .addFilter("createdAt", Operator.GT, Operator.LT, Operator.GTE, Operator.LTE, Operator.BETWEEN)
    )
    .requestBody(configurer -> configurer
        .addFilter("role", Operator.EQ, Operator.NEQ, Operator.IN)
        .addFilter("firstName", Operator.EQ, Operator.CONTAINS, Operator.ICONTAINS)
        .addFilter("lastName", Operator.EQ, Operator.CONTAINS, Operator.ICONTAINS)
        .addFilter("active", "isActive", Operator.EQ) // with alias
        .addFilter("lastLogin", Operator.GT, Operator.LT, Operator.GTE, Operator.LTE, Operator.BETWEEN)
        .addFilter("createdAt", Operator.GT, Operator.LT, Operator.GTE, Operator.LTE, Operator.BETWEEN)
        .addFilter("createdBy.firstName", Operator.EQ) // Nested field example
        .addCustomFilter("customRoleFilter", User.Role.class,
            (root, criteriaQuery, cb, values, filterErrorWrapper) ->
                Optional.of(cb.equal(root.get("role"), values.get(0)))
        )
    )
    .buildTemplate();

SortContext.Template<User> userSortTemplate = SortContext.buildTemplateForType(User.class)
    .queryParam(configurer -> configurer
        .addSorts("firstName") // Adds both ASC and DESC
        .addSorts("lastName")
        .addSorts("createdAt", (root, cq, cb) -> root.get("createdAt")) // Can provide expression
        .addSorts("role")
        .addDescSort("createdBy.firstName") // Nested field sort
        .addAscSort("createdBy.lastName")
        .addCustomSort("customSort",
            (root, cq, cb, errorWrapper) -> 
                Optional.of(cb.asc(root.get("firstName")))
        )
    )
    .requestBody(configurer -> configurer
        .addSorts(Set.of("first-name", "name"), "firstName") // with set of aliases
        .addSorts("lastName")
        .addSorts("createdDateTime", "createdAt") // with alias
        .addSorts("role"))
    .buildTemplate();

Field Aliases (filtering & sorting)

The library supports registering client-facing aliases for entity fields. Aliases allow you to expose stable, concise, or more user-friendly names in your API while mapping them to the actual entity field names used by the JPA code.

  • When you call the convenience methods like addFilter(fieldName, ...) or addSort(fieldName, ...), the template will register a default alias where the alias equals the entity field name. This preserves backwards compatibility for clients that already use entity field names directly.
  • To explicitly register an alias use the overloads that accept an alias (or a set of aliases). The alias is what clients will include in query parameters or request bodies; the library will map that alias to the configured entity field.
  • The final built FilterContext / SortContext exposes the alias-to-field mapping via getAliasToFieldMap() so you can inspect or document what aliases are available.

Example — register aliases for filters and sorts:

// register a single alias 'givenName' that maps to entity field 'firstName'
userFilterTemplate = FilterContext.buildTemplateForType(User.class)
    .requestBody(cfg -> cfg.addFilter("givenName", "firstName", Operator.EQ));

// register multiple aliases for a sort: 'name' and 'fullname' map to 'firstName'
userSortTemplate = SortContext.buildTemplateForType(User.class)
    .queryParam(cfg -> cfg.addSort(Set.of("name", "fullname"), "firstName"));

Note: aliases must be unique across the template — attempting to register the same alias for different fields will result in an IllegalArgumentException at template build time.

Building Contexts from Requests

// For query parameters (e.g., from HttpServletRequest)
FilterContext<User> filterContext = userFilterTemplate.newSourceBuilder()
    .withQuerySource(request)
    .buildFilterContext();

SortContext<User> sortContext = userSortTemplate.newSourceBuilder()
    .withQuerySource(request)
    .buildSortContext();

// For request body (e.g., from controller DTOs)
FilterContext<User> filterContextFromBody = userFilterTemplate.newSourceBuilder()
    .withBodySource(filterRequests)
    .buildFilterContext();

SortContext<User> sortContextFromBody = userSortTemplate.newSourceBuilder()
    .withBodySource(sortRequests)
    .buildSortContext();

// Providing both query parameters and request body
FilterContext<User> filterContextWithBoth = userFilterTemplate.newSourceBuilder()
    .withQuerySource(request)
    .withBodySource(filterRequests)
    .buildFilterContext();

SortContext<User> sortContextWithBoth = userSortTemplate.newSourceBuilder()
    .withQuerySource(request)
    .withBodySource(sortRequests)
    .buildSortContext();

Applying Specifications to Repository

Specification<User> filterSpec = queryFilterBuilder.buildFilterSpecification(filterContext);
Specification<User> sortSpec = queryFilterBuilder.buildSortSpecification(sortContext);

// Combine and use with Spring Data JPA
List<User> users = userRepository.findAll(Specification.allOf(filterSpec, sortSpec));

Nested Field Filtering and Sorting

// Filtering on nested property (e.g., createdBy.firstName)
FilterContext<User> filterContext = userFilterTemplate.newSourceBuilder()
    .withQuerySource(request)
    .withBodySource(filterRequests)
    .buildFilterContext();
// Example query param: `?createdBy.firstName.contains=John`
// Example request body: `[{"field": "createdBy.firstName", "operator": "contains", "value": "John"}]`


// Sorting on nested property (e.g., createdBy.lastName DESC)
SortContext<User> sortContext = userSortTemplate.newSourceBuilder()
    .withQuerySource(request)
    .withBodySource(sortRequests)
    .buildSortContext();
// Example query param: `?sort=-createdBy.lastName`
// Example request body: `[{"field": "createdBy.lastName", "direction": "ASC"}]`

Fetch reuse and custom fetches

When resolving nested fields that were declared using the template's normal field methods (i.e. fields added by name), and not fields that use an expression provider or a custom filter/sort function, the library will reuse association fetches that already exist on the query root. In practical terms this means:

  • If your code (or a custom Specification) adds a fetch/association on the root before the package builds its own specification, the library will detect and reuse those fetches rather than creating duplicate joins.
  • If you supply your own Specification or pre-configured context and apply it before the specification generated by this package, any fetches you added can be reused by the generated specification. This ordering can lead to behavior that looks unexpected if you assume the generated specification will be the first to configure associations.

Guidance to avoid surprises:

  • Be intentional about the order in which you apply custom specifications and the package-generated specification. If you want the package to create its own associations, avoid pre-applying custom specs that add fetches.
  • If you need custom fetches to be used by the generated specification, add them deliberately (for example in your template or custom specification) before combining specifications.
  • Keep specifications small and well-documented: clearly document any custom fetches so other developers understand the ordering and reuse behavior.

This behavior is designed to avoid duplicate joins and to respect user-provided query modifications, but it can be surprising if you are not expecting fetches from previously-applied specifications to be reused.

Example — reusing a custom fetch

If you want the generated specification to reuse a fetch you added, apply a small Specification that performs the fetch before combining it with the generated specs. For example:

Specification<Book> fetches = (root, cq, cb) -> {
    root.fetch("author", JoinType.LEFT);
    return null;
};

Specification<Book> filterSpecs = queryFilterBuilder.buildFilterSpecification(filterContext);
Specification<Book> sortSpecs = queryFilterBuilder.buildSortSpecification(sortContext);

Page<Book> books = bookRepository.findAll(Specification.allOf(fetches, filterSpecs, sortSpecs), pageable);

Note: if the fetch is created with JoinType.LEFT or JoinType.RIGHT, the package will reuse that fetch as-is (the same join type will be respected).

Using ListAPIRequest in Controller and Service

You can use the provided ListAPIRequest DTO to accept filtering and sorting criteria in a structured way, making your API endpoints consistent and predictable. Below is a complete example of a controller and service using ListAPIRequest:

Controller Example

import jakarta.validation.Valid;

@RestController
@RequestMapping("/users")
public class UserController {
    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping("/list")
    public ResponseEntity<List<User>> listUsers(@Valid @RequestBody ListAPIRequest request) {
        List<User> users = userService.listUsers(request);
        return ResponseEntity.ok(users);
    }
}

Service Example

import java.util.Objects;

@Service
public class UserService {
    private final QueryFilterBuilder<User> queryFilterBuilder;
    private final UserRepository userRepository;
    private final FilterContext.Template<User> userFilterTemplate;
    private final SortContext.Template<User> userSortTemplate;

    public UserService(QueryFilterBuilder<User> queryFilterBuilder,
                      UserRepository userRepository) {
        this.queryFilterBuilder = queryFilterBuilder;
        this.userRepository = userRepository;
        // templates initialized elsewhere (in real code these would be provided/injected)
    }

    public List<User> listUsers(ListAPIRequest request) {
        Objects.requireNonNull(userFilterTemplate, "userFilterTemplate must be initialized");
        Objects.requireNonNull(userSortTemplate, "userSortTemplate must be initialized");
        FilterContext<User> filterContext = userFilterTemplate.newSourceBuilder()
            .withBodySource(request.filters())
            .buildFilterContext();
        SortContext<User> sortContext = userSortTemplate.newSourceBuilder()
            .withBodySource(request.sorts())
            .buildSortContext();
        Specification<User> filterSpecification = queryFilterBuilder.buildFilterSpecification(filterContext);
        Specification<User> sortSpecification = queryFilterBuilder.buildSortSpecification(sortContext);
        return userRepository.findAll(Specification.allOf(filterSpecification, sortSpecification));
    }
}

This approach ensures maintainable, consistent, and predictable results for your API endpoints.

Supported Operators

Operator literals are case-insensitive: whether sent in query parameters or request body, any case (e.g., EQ, eq, Eq, eQ) will be accepted and correctly interpreted.

Operator Description
eq Equal to
neq Not equal to
gt Greater than
gte Greater than or equal to
lt Less than
lte Less than or equal to
in In a list of values
notIn Not in a list of values
isNull Is null
isNotNull Is not null
contains Contains string
icontains Case-insensitive contains
startsWith Starts with
istartsWith Case-insensitive starts with
endsWith Ends with
iendsWith Case-insensitive ends with
between Between two values
notBetween Not between two values

Supported Types

Query Filter Builder supports a wide range of types for filtering and sorting, classified as follows:

Numeric/Boolean Types

  • Byte, Short, Integer, Long, Float, Double, BigDecimal, BigInteger, Boolean

Text Types

  • Character, String, Any Java enum type (enums are treated as text for filtering and sorting)

Special Types

  • UUID

Date and Time Types

  • Instant, OffsetDateTime, ZonedDateTime, OffsetTime, LocalDateTime, LocalDate, LocalTime, YearMonth, Year

Advanced Features

Custom Filters

Custom filters allow for complex logic beyond standard operators. Define them in the template using addCustomFilter.

See the Defining Filter and Sort Templates example for usage.

Custom Sorts

Custom sorts allow for complex logic beyond simple sorting. Define them in the template using addCustomSort.

See the Defining Filter and Sort Templates example for usage.

Enum Filter Implementation

Query Filter Builder provides a default implementation for enum filtering via EnumFilterFieldImp, which uses Enum.valueOf(Class, String) for casting string values to enum types. This is suitable for most use cases where enum names are matched exactly.

Custom Enum Filter Implementation

If you need to customize how enum values are parsed (e.g., case-insensitive matching, mapping from other string representations), you can provide your own implementation by subclassing AbstractEnumFilterField and overriding the cast(Class<T>, String) method.

To use your custom implementation, annotate your bean with @Primary so that Spring will inject it instead of the default:

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

@Primary
@Component
public class MyCustomEnumFilterField extends AbstractEnumFilterField {
    @Override
    public <T extends Enum<T>> T cast(Class<T> enumClass, String value) {
        // Custom logic, e.g., case-insensitive
        for (T constant : enumClass.getEnumConstants()) {
            if (constant.name().equalsIgnoreCase(value)) {
                return constant;
            }
        }
        throw new IllegalArgumentException("No enum constant " + enumClass.getName() + "." + value);
    }
}

Spring will automatically use your implementation for enum filtering if it is marked as @Primary.

Sorting via HTTP Query Parameters

When sorting via HTTP query parameters, the sort parameter defines the fields and their sorting order. The format is as follows:

sort=-field1,field2,field3,...
  • A leading - before a field name indicates descending order.
  • If no - is present, the field is sorted in ascending order.
  • Multiple fields can be specified, separated by commas. The sorting respects the order of fields as they appear in the query parameter.

Example:

sort=-firstName,lastName,createdAt
  • firstName will be sorted in descending order.
  • lastName will be sorted in ascending order.
  • createdAt will be sorted in ascending order.

Sorting via Request Body

  • The order of fields in the request body determines the sorting precedence.
  • Sorting direction literals are case-insensitive. Any case (e.g., ASC, asc, aSc, DESC, desc, dEsC) will be accepted and correctly interpreted.
  • Defaults to ASC if no direction is specified or if the direction is invalid string.

This ensures that the sorting process is predictable and respects the client's specified order, whether provided in query parameters or the request body.

Edge Cases & Error Handling

  • Request Body Override: If a filter or sort field is provided in both query parameters and the request body, the value from the request body will take precedence and override the one from the query parameters.
  • If a field passed via query parameters or request body is not defined in the filter/sort template, it is silently ignored.
  • If the entity class is not a JPA @Entity, an exception is thrown.
  • If the context or required arguments are null, a NullPointerException is thrown.
  • If no valid filters or sorts are found, the resulting specification will not filter or sort any results.
  • Unsupported types will throw an error at template build time.
  • In all other error scenarios, an exception is thrown to ensure predictable behavior and easier debugging. This includes:
    • Invalid value for a field (e.g., "abc" for a number).
    • Invalid operator for a field.
    • Invalid field path for nested properties.
    • Misconfiguration of templates.
  • The specific exceptions thrown are detailed in the Exception Handling section.

Exception Handling

Query Filter Builder provides robust exception handling to ensure predictable error responses and easier debugging.

Built-in Exception Handler

  • The package provides a base exception handler class (QueryFilterBuilderExceptionHandler). To activate exception handling, you must subclass this base class in your application and annotate it with @ControllerAdvice. This enables automatic handling of exceptions thrown by the library and returns meaningful error responses in your Spring Boot application.
  • You can further customize the error handling by overriding methods in your subclass.

Exceptions Thrown

- InvalidQueryParameterException
  • Description: Thrown when a query parameter or request body contains invalid, unrecognized, or unconvertible values (e.g., invalid value).
  • Handling: The exception handler will return a clear error message indicating which parameter or value was invalid, helping clients correct their requests.
- QueryBuilderConfigurationException
  • Description: Thrown for internal errors within the filter builder, such as misconfiguration, template errors, or unexpected failures during query construction.
  • Handling: The exception handler will return a clear error message indicating which parameter or value was invalid, helping developers correct their templates.

For custom error handling, you can provide your own @RestControllerAdvice and handle these exceptions as needed.

Examples

Filtering and Sorting Users

// Assume userFilterTemplate and userSortTemplate are defined as above
FilterContext<User> filterContext = userFilterTemplate.newSourceBuilder()
    .withQuerySource(request)
    .buildFilterContext();
SortContext<User> sortContext = userSortTemplate.newSourceBuilder()
    .withQuerySource(request)
    .buildSortContext();

Specification<User> filterSpec = queryFilterBuilder.buildFilterSpecification(filterContext);
Specification<User> sortSpec = queryFilterBuilder.buildSortSpecification(sortContext);

List<User> users = userRepository.findAll(filterSpec.and(sortSpec));

Filtering on Nested Fields

// Example: Filter users by their creator's first name
FilterContext<User> filterContext = userFilterTemplate.newSourceBuilder()
    .withQuerySource(request)
    .buildFilterContext();
// Query param: ?createdBy.firstName=John

Sorting on Nested Fields

// Example: Sort users by their creator's last name descending
SortContext<User> sortContext = userSortTemplate.newSourceBuilder()
    .withQuerySource(request)
    .buildSortContext();
// Query param: ?sort=-createdBy.lastName

DTOs for Consistent API Design

ListAPIRequest

The package provides a convenient ListAPIRequest DTO, which encapsulates lists of FilterRequest and SortRequest objects. This DTO is ready to use in any controller method, allowing you to accept filtering and sorting criteria in a structured and predictable way:

import jakarta.validation.Valid;

@PostMapping("/users/list")
public ResponseEntity<List<User>> listUsers(@Valid @RequestBody ListAPIRequest request) {
    Specification<User> filterSpecs = queryFilterBuilder.buildFilterSpecification(
        userFilterTemplate.newSourceBuilder().withBodySource(request.filters()).buildFilterContext()
    );
    Specification<User> sortSpecs = queryFilterBuilder.buildSortSpecification(
        userSortTemplate.newSourceBuilder().withBodySource(request.sorts()).buildSortContext()
    );
    List<User> users = userRepository.findAll(Specification.allOf(filterSpecs, sortSpecs));
    return ResponseEntity.ok(users);
}

Using ListAPIRequest helps standardize your API endpoints, making development more consistent and results more predictable. It also improves maintainability by providing a single DTO for both filtering and sorting operations.

Public API Reference

FilterContext

Static Entry Point

  • buildTemplateForType(Class<T> type): Creates a TemplateBuilder for the given entity type. This is the entry point for defining a filter template. The class must be annotated with @Entity.

TemplateBuilder Methods

  • queryParam(Consumer<FilterConfigurer<T>> configurer): Configures filters that are sourced from query parameters. Accepts a consumer that provides a FilterConfigurer to define the filters.
  • requestBody(Consumer<FilterConfigurer<T>> configurer): Configures filters that are sourced from the request body. Accepts a consumer that provides a FilterConfigurer to define the filters.
  • buildTemplate(): Builds the final immutable Template instance containing all filter configurations.

Template Methods

  • newSourceBuilder(): Creates a new SourceBuilder from this template, which can then be used to specify the source of the filter data (query parameters or request body).

SourceBuilder Methods

  • withQuerySource(HttpServletRequest request): Specifies that the filter data will be sourced from an HttpServletRequest (i.e., query parameters).
  • withBodySource(List<FilterRequest> filterRequests): Specifies that the filter data will be sourced from a list of FilterRequest objects (i.e., request body).
  • buildFilterContext(): Builds the final FilterContext instance containing the template configuration and the data source.

FilterConfigurer Methods

  • addFilter(String fieldName, Operator... operators): Adds a filter for the specified field with the given operators. The field name is used as its default alias for backward compatibility.
  • addFilter(String fieldName, ExpressionProviderFunction<T, K> expressionProviderFunction, Operator... operators): Adds a filter with a custom expression provider. Useful when filter logic requires more than simple field comparison. The field name is used as default alias.
  • addFilter(String alias, String fieldName, Operator... operators): Adds a filter for the given entity field and registers a single client-facing alias for it. The alias is what clients will use when submitting filter parameters.
  • addFilter(String alias, String fieldName, ExpressionProviderFunction<T, K> expressionProviderFunction, Operator... operators): Adds a filter backed by a custom expression provider and registers a single client-facing alias for it.
  • addFilter(Set<String> aliases, String fieldName, Operator... operators): Adds a filter for the given entity field and registers multiple client-facing aliases that map to it. All aliases will be associated with the same field.
  • addFilter(Set<String> aliases, String fieldName, ExpressionProviderFunction<T, K> expressionProviderFunction, Operator... operators): Adds a filter that uses a custom expression provider and registers multiple client-facing aliases for it.
  • addCustomFilter(String filterName, Class<K> dataTypeForInput, CustomFilterFunction<T> filterFunction): Adds a custom filter with a specific name, data type, and filter function. Custom filters allow for complex filtering logic that cannot be expressed with standard operators.

SortContext

Static Entry Point

  • buildTemplateForType(Class<T> type): Creates a TemplateBuilder for the given entity type. This is the entry point for defining a sort template. The class must be annotated with @Entity.

TemplateBuilder Methods

  • queryParam(Consumer<SortConfigurer<T>> configurer): Configures sorts that are sourced from query parameters. Accepts a consumer that provides a SortConfigurer to define the sorts.
  • requestBody(Consumer<SortConfigurer<T>> configurer): Configures sorts that are sourced from the request body. Accepts a consumer that provides a SortConfigurer to define the sorts.
  • buildTemplate(): Builds the final immutable Template instance containing all sort configurations.

Template Methods

  • newSourceBuilder(): Creates a new SourceBuilder from this template, which can then be used to specify the source of the sort data (query parameters or request body).

SourceBuilder Methods

  • withQuerySource(HttpServletRequest request): Specifies that the sort data will be sourced from an HttpServletRequest (i.e., query parameters).
  • withBodySource(List<SortRequest> sortRequests): Specifies that the sort data will be sourced from a list of SortRequest objects (i.e., request body).
  • buildSortContext(): Builds the final SortContext instance containing the template configuration and the data source.

SortConfigurer Methods - Field Name Based (No Alias)

  • addAscSort(String fieldName): Adds an ascending sort for the specified field. Registers a default alias where the alias equals the field name for backward compatibility.
  • addDescSort(String fieldName): Adds a descending sort for the specified field. Registers a default alias where the alias equals the field name.
  • addSorts(String fieldName): Adds both ascending and descending sort options for the specified field. Registers a default alias where the alias equals the field name.
  • addAscSort(String fieldName, ExpressionProviderFunction<T, K> expressionProviderFunction): Adds an ascending sort with a custom expression provider. Useful when sort logic requires more than simple field comparison. Registers a default alias.
  • addDescSort(String fieldName, ExpressionProviderFunction<T, K> expressionProviderFunction): Adds a descending sort with a custom expression provider. Registers a default alias.
  • addSorts(String fieldName, ExpressionProviderFunction<T, K> expressionProviderFunction): Adds both ascending and descending sort options with a custom expression provider. Registers a default alias.

SortConfigurer Methods - Single Alias

  • addAscSort(String alias, String fieldName): Registers a single client-facing alias that maps to the given entity field and allows ascending sorting for that field.
  • addDescSort(String alias, String fieldName): Registers a single client-facing alias that maps to the given entity field and allows descending sorting for that field.
  • addSorts(String alias, String fieldName): Registers a single client-facing alias that maps to the given entity field and allows both ascending and descending sorting for that field.
  • addAscSort(String alias, String fieldName, ExpressionProviderFunction<T, K> expressionProviderFunction): Registers a single client-facing alias that maps to the given field and attaches an ExpressionProviderFunction to compute the ascending sort expression.
  • addDescSort(String alias, String fieldName, ExpressionProviderFunction<T, K> expressionProviderFunction): Registers a single client-facing alias that maps to the given field and attaches an ExpressionProviderFunction to compute the descending sort expression.
  • addSorts(String alias, String fieldName, ExpressionProviderFunction<T, K> expressionProviderFunction): Registers a single client-facing alias that maps to the given field and attaches an ExpressionProviderFunction to compute both ascending and descending sort expressions.

SortConfigurer Methods - Multiple Aliases

  • addAscSort(Set<String> aliases, String fieldName): Registers multiple client-facing aliases that all map to the same entity field and allow ascending sorting for that field.
  • addDescSort(Set<String> aliases, String fieldName): Registers multiple client-facing aliases that all map to the same entity field and allow descending sorting for that field.
  • addSorts(Set<String> aliases, String fieldName): Registers multiple client-facing aliases that all map to the same entity field and allow both ascending and descending sorting for that field.
  • addAscSort(Set<String> aliases, String fieldName, ExpressionProviderFunction<T, K> expressionProviderFunction): Registers multiple client-facing aliases that map to the same entity field and attaches an ExpressionProviderFunction to compute the ascending sort expression.
  • addDescSort(Set<String> aliases, String fieldName, ExpressionProviderFunction<T, K> expressionProviderFunction): Registers multiple client-facing aliases that map to the same entity field and attaches an ExpressionProviderFunction to compute the descending sort expression.
  • addSorts(Set<String> aliases, String fieldName, ExpressionProviderFunction<T, K> expressionProviderFunction): Registers multiple client-facing aliases that map to the same entity field and attaches an ExpressionProviderFunction to compute both ascending and descending sort expressions.

SortConfigurer Methods - Custom Sort

  • addCustomSort(String sortName, CustomSortFunction<T> sortFunction): Adds a custom sort with a specific name and sort function. Custom sorts allow for complex sorting logic that cannot be expressed with standard field-based sorting.

QueryFilterBuilder

  • buildFilterSpecification(FilterContext<T> filterContext): Builds a JPA Specification for filtering based on the provided FilterContext.
  • buildSortSpecification(SortContext<T> sortContext): Builds a JPA Specification for sorting based on the provided SortContext.

For more advanced usage, see the integration tests and Javadoc.


Contributing

We welcome contributions from the community to make Query Filter Builder even better! Please refer to the CONTRIBUTING.md file for detailed guidelines on how to contribute to the project.


License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

About

A robust and versatile library designed to dynamically generate type-safe JPA queries from HTTP query parameters and request bodies in Spring Boot applications.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages