<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:
- Configuration for Query By Spec
- Registration of the default
SpecMapper
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
.