SpecMapper

SpecMapper is the most important class and serves as the API entry point for all specification operations:

var mapepr = SpecMapper.builder().build();

Next, we define a POJO (Plain Old Java Object) that encapsulates the query conditions, such as:

@Data
public class CustomerCriteria {

  @Spec(Like.class)
  String firstname;
}

With this, we can perform the conversion to a Specification. Once we have the Specification, we can query the database using the original approach, for example, through the repository of Spring Data JPA:

var criteria = new CustomerCriteria();
criteria.setFirstname("Hello")

var mapper = SpecMapper.builder().build();
var specification = mapper.toSpec(criteria);

customerRepository.findAll(specification);

The executed SQL will be like:

... where x.firstname like '%Hello%'

Skipping Strategy

In the fields of the POJO, if any of the following conditions are met, they will be ignored during the conversion process:

  • No Spec Annotation is attached.
  • The value is null.
  • If the type is Iterable and the value is empty.
  • If the type is Optional and the value is empty.
  • If the type is CharSequence and the length of value is 0.
  • If the type is Array and the length of value is 0.
  • If the type is Map and the value is empty.

For example, after constructing the following POJO, if no values are set and it is directly converted into a Specification for querying:

@Data
public class CustomerCriteria {

  @Spec(Like.class)
  String firstname;
  
  String lastname = "Hello";
   
  @Spec
  String nickname = "";
  
  @Spec(GreaterThat.class)
  Optional<Integer> age = Optional.empty();
  
  @Spec(In.class)
  Collection<String> addresses = Arrays.asList();
}

var mapper = SpecMapper.builder().build();
customerRepository.findAll(mapper.toSpec(new CustomerCriteria()));

The executed SQL in the above example will not have any filtering conditions.

If you are using the Builder Pattern (e.g., Lombok’s @Builder), please pay special attention to the default values set in the builder.

If you want to customize the logic for skipping, you can implement a SkippingStrategy and pass it when constructing a SpecMapper:

var mapper = SpecMapper.builder()
      .defaultResolvers()
      .skippingStrategy(fieldValue -> {
        // Determine whether to skip the field value and return a boolean
      })
      .build();