This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Starter

Spring Starter
    <dependency>
      <groupId>tw.com.softleader.data.jakarta</groupId>
      <artifactId>specification-mapper-starter</artifactId>
      <version>${specification-mapper.version}</version>
    </dependency>
    

    The specification-mapper-starter integrates specification-mapper with Spring Data JPA and provides a way to query by specifications.

    Query by Spec (QBS) is a user-friendly querying approach that allows you to dynamically build query conditions using specifications. With the QBS interface, you can execute query statements easily.

    Getting Started

    By adding the dependency in your pom.xml file, the specification-mapper-starter will automatically configure everything during the Spring Boot startup process, allowing you to start using it without any additional configuration. The starter includes the following features:

    The auto-configuration is enabled by default, and you can control it through the spec.mapper.enabled property in your application’s properties file. To disable the auto-configuration, you can use the following configuration:

    spec:
      mapper:
        enabled: false
    

    Query by Spec

    Query by Spec (QBS) provides the QueryBySpecExecutor<T> interface, which includes several query methods:

    public interface QueryBySpecExecutor<T> {
    
      List<T> findBySpec(Object spec);
      
      List<T> findBySpec(Object spec, Sort sort);
      
      Page<T> findBySpec(Object spec, Pageable pageable);
      
      // … more functionality omitted.
    }
    

    To use these methods, you simply need to extend QueryBySpecExecutor<T> in your existing repository interface:

    public interface PersonRepository 
      extends JpaRepository<Person, Long>, QueryBySpecExecutor<Person> {
      ...
    }
    
    @Service
    public class PersonService {
    
      @Autowired PersonRepository personRepository;
    
      public List<Person> findPeople(PersonCriteria criteria) {
        return personRepository.findBySpec(criteria);
      }
    }
    

    By inheriting QueryBySpecExecutor<T>, you can directly use the query methods in your repository interface, making it easy to perform queries using specifications.

    Customize the QBS Base Repository

    During the configuration process, QBS automatically configures the Spring Data JPA Base Repository. The default implementation is QueryBySpecExecutorImpl.

    However, since Java only supports single inheritance, and to allow your application to retain its original parent Base Repository, QBS provides an extension point called QueryBySpecExecutorAdapter.

    Depending on your application’s needs, you can choose to either extend QueryBySpecExecutorImpl or implement QueryBySpecExecutorAdapter to customize the Base Repository. For example:

    class MyRepositoryImpl<T, ID> extends SimpleJpaRepository<T, ID>
      implements QueryBySpecExecutorAdapter<T> {
    
      @Setter
      @Getter
      private SpecMapper specMapper;
    
      private final EntityManager entityManager;
    
      MyRepositoryImpl(JpaEntityInformation entityInformation,
                              EntityManager entityManager) {
        super(entityInformation, entityManager);
    
        // Keep the EntityManager around to be used from the newly introduced methods.
        this.entityManager = entityManager;
      }
    
      @Override
      public Class<T> getDomainClass() {
        return super.getDomainClass();
      }
      
      @Transactional
      public <S extends T> S mySave(S entity) {
        // implementation goes here
      }
    }
    

    You can configure your custom Base Repository by setting the spec.mapper.repository-base-class property in your application’s properties file, specifying the full package name of your custom base repository, like this:

    spec:
      mapper:
        repository-base-class: com.acme.example.MyRepositoryImpl
    

    Default SpecMapper

    This starter automatically configures a Default SpecMapper during the application startup process and registers it as a Spring @Bean. You can obtain an instance of the SpecMapper through dependency injection (@Autowired) in your application.

    For example, if you want to enhance the specifications before performing the query, you can use the SpecMapper as follows:

    class PersonService {
    
      @Autowired SpecMapper specMapper;
      @Autowired PersonRepository personRepository;
    
      List<Person> getPersonByCriteria(PersonCriteria criteria) {
        var spec = specMapper.toSpec(criteria);
        
        // Perform additional operations on the spec, ex:
        // spec = spec.and((root, query, criteriaBuilder) ->  {
        //     ...
        // });
        
        return personRepository.findAll(spec);
      }
    }
    

    In the above example, the SpecMapper is injected into the PersonService, allowing you to convert the criteria into a specification using specMapper.toSpec(). You can then modify the spec as needed before passing it to the personRepository for querying.

    Customize SpecificationResolver

    By registering your custom SpecificationResolver as a Spring @Bean, it will be automatically detected and added to the Default SpecMapper during the application startup process.

    For example, if you want to add your custom spec annotation as mentioned in Customize Spec Annotation, you can configure it as follows:

    @Configuration
    class MyConfig {
    
      @Bean
      SpecificationResolver myResolver() {
        return ...
      }
    }
    

    If your SpecificationResolver needs access to the SpecMapper itself, you can wrap it in a SpecificationResolverCodecBuilder. This way, the SpecCodec, which is the interface of SpecMapper, will be passed in when constructing the resolver. Here’s an example:

    @Configuration
    class MyConfig {
    
      @Bean
      SpecificationResolverCodecBuilder myResolver() {
        return MySpecificationResolver::new;
      }
    }
    
    class MySpecificationResolver implements SpecificationResolver {
      
      private final SpecCodec codec;
      
      MySpecificationResolver(SpecCodec codec) {
        // Keep the SpecCodec around to be used.
        this.codec = codec;
      }
      
      // implementation goes here
    }
    

    In the above example, the MySpecificationResolver is constructed with the SpecCodec provided by the SpecMapper. This allows you to access and utilize the SpecMapper functionality within your custom resolver.

    Customize SkippingStrategy

    As long as you register your custom SkippingStrategy as a Spring @Bean, it will automatically be detected and added to the Default SpecMapper during the application startup process!

    Here’s a configuration example:

    @Configuration
    class MyConfig {
    
      @Bean
      SkippingStrategy mySkippingStrategy() {
        return ...
      }
    }
    

    Customize ASTWriterFactory

    Through the spec.mapper.impersonate-logger property, you can set whether the Logging process should impersonate the logger of the actual object being processed. It is turned off by default. To enable it, see the example below:

    spec:
      mapper:
        # Whether to impersonate the logger of the actual object being processed, off by default
        impersonate-logger: true
    

    If you need full customization, simply register your custom ASTWriterFactory as a Spring @Bean. It will automatically be detected and added to the Default SpecMapper during the app startup!

    Here’s how to configure it:

    @Configuration
    class MyConfig {
    
      @Bean
      ASTWriterFactory myASTWriterFactory() {
        return ...
      }
    }
    

    Customize Default SpecMapper

    Certainly, you can fully customize the SpecMapper. Just register your SpecMapper as a Spring @Bean, and during the application startup process, the configuration of the Default SpecMapper will be skipped in favor of the one you registered.

    Here’s an example configuration:

    @Configuration
    class MyConfig {
    
      @Bean
      SpecMapper mySpecMapper() {
        return SpecMapper.builder()
          . ...
          .build();
      }
    }
    

    In the above example, you can customize the SpecMapper according to your needs by providing the desired configuration options within the mySpecMapper method. This way, the application will use the SpecMapper instance that you registered as a @Bean, overriding the default configuration of the SpecMapper.