I needed some custom QueryDSL enabled query methods and followed this SO answer.
That worked great, but after upgrading to Spring Boot 2.1 (which upgrades Spring Dat
In Spring Data JPA 2.1.6 the constructor of QuerydslJpaPredicateExecutor has changed.
I present here an alternative approach using a wrapper to https://stackoverflow.com/a/53960209/3351474. This makes the solution independent from the internals of Spring Data JPA. Three classes have to be implemented.
As an example I take here a customized Querydsl implementation using always the creationDate of an entity as sort criteria if nothing is passed. I assume in this example that this column exists in some @MappedSuperClass for all entities. Use generated static metadata in real life instead the hard coded string "creationDate".
As first the wrapped delegating all CustomQuerydslJpaRepositoryIml delegating all methods to the QuerydslJpaPredicateExecutor:
/**
* Customized Querydsl JPA repository to apply custom filtering and sorting logic.
*
*/
public class CustomQuerydslJpaRepositoryIml implements QuerydslPredicateExecutor {
private final QuerydslJpaPredicateExecutor querydslPredicateExecutor;
public CustomQuerydslJpaRepositoryIml(QuerydslJpaPredicateExecutor querydslPredicateExecutor) {
this.querydslPredicateExecutor = querydslPredicateExecutor;
}
private Sort applyDefaultOrder(Sort sort) {
if (sort.isUnsorted()) {
return Sort.by("creationDate").ascending();
}
return sort;
}
private Pageable applyDefaultOrder(Pageable pageable) {
if (pageable.getSort().isUnsorted()) {
Sort defaultSort = Sort.by(AuditableEntity_.CREATION_DATE).ascending();
pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), defaultSort);
}
return pageable;
}
@Override
public Optional findOne(Predicate predicate) {
return querydslPredicateExecutor.findOne(predicate);
}
@Override
public List findAll(Predicate predicate) {
return querydslPredicateExecutor.findAll(predicate);
}
@Override
public List findAll(Predicate predicate, Sort sort) {
return querydslPredicateExecutor.findAll(predicate, applyDefaultOrder(sort));
}
@Override
public List findAll(Predicate predicate, OrderSpecifier>... orders) {
return querydslPredicateExecutor.findAll(predicate, orders);
}
@Override
public List findAll(OrderSpecifier>... orders) {
return querydslPredicateExecutor.findAll(orders);
}
@Override
public Page findAll(Predicate predicate, Pageable pageable) {
return querydslPredicateExecutor.findAll(predicate, applyDefaultOrder(pageable));
}
@Override
public long count(Predicate predicate) {
return querydslPredicateExecutor.count(predicate);
}
@Override
public boolean exists(Predicate predicate) {
return querydslPredicateExecutor.exists(predicate);
}
}
Next the CustomJpaRepositoryFactory doing the magic and providing the Querydsl wrapper class instead of the default one. The default one is passed as parameter and wrapped.
/**
* Custom JpaRepositoryFactory allowing to support a custom QuerydslJpaRepository.
*
*/
public class CustomJpaRepositoryFactory extends JpaRepositoryFactory {
/**
* Creates a new {@link JpaRepositoryFactory}.
*
* @param entityManager must not be {@literal null}
*/
public CustomJpaRepositoryFactory(EntityManager entityManager) {
super(entityManager);
}
@Override
protected RepositoryComposition.RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) {
final RepositoryComposition.RepositoryFragments[] modifiedFragments = {RepositoryComposition.RepositoryFragments.empty()};
RepositoryComposition.RepositoryFragments fragments = super.getRepositoryFragments(metadata);
// because QuerydslJpaPredicateExecutor is using som internal classes only a wrapper can be used.
fragments.stream().forEach(
f -> {
if (f.getImplementation().isPresent() &&
QuerydslJpaPredicateExecutor.class.isAssignableFrom(f.getImplementation().get().getClass())) {
modifiedFragments[0] = modifiedFragments[0].append(RepositoryFragment.implemented(
new CustomQuerydslJpaRepositoryIml((QuerydslJpaPredicateExecutor) f.getImplementation().get())));
} else {
modifiedFragments[0].append(f);
}
}
);
return modifiedFragments[0];
}
}
Finally the CustomJpaRepositoryFactoryBean. This must be registered with the Spring Boot application, to make Spring aware where to get the repository implementations from, e.g. with:
@SpringBootApplication
@EnableJpaRepositories(basePackages = "your.package",
repositoryFactoryBeanClass = CustomJpaRepositoryFactoryBean.class)
...
Here now the class:
public class CustomJpaRepositoryFactoryBean, S, I> extends JpaRepositoryFactoryBean {
/**
* Creates a new {@link JpaRepositoryFactoryBean} for the given repository interface.
*
* @param repositoryInterface must not be {@literal null}.
*/
public CustomJpaRepositoryFactoryBean(Class extends T> repositoryInterface) {
super(repositoryInterface);
}
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new CustomJpaRepositoryFactory(entityManager);
}
}