Replacing deprecated QuerydslJpaRepository with QuerydslJpaPredicateExecutor fails
With Spring Boot 2.1.1 the following solution may help you. The key is to extend JpaRepositoryFactory
and override the method getRepositoryFragments(RepositoryMetadata metadata)
. In this method you can provide base (or more specific fragment) implementations for any custom repository which should be taken for every extending repository.
Let me show you an example:
QueryableReadRepository:
@NoRepositoryBean
public interface QueryableReadRepository<T> extends Repository<T, String> {
List<T> findAll(Predicate predicate);
List<T> findAll(Sort sort);
List<T> findAll(Predicate predicate, Sort sort);
List<T> findAll(OrderSpecifier<?>... orders);
List<T> findAll(Predicate predicate, OrderSpecifier<?>... orders);
Page<T> findAll(Pageable page);
Page<T> findAll(Predicate predicate, Pageable page);
Optional<T> findOne(Predicate predicate);
boolean exists(Predicate predicate);
}
The following interface combines different repositories.
DataRepository:
@NoRepositoryBean
public interface DataRepository<T>
extends CrudRepository<T, String>, QueryableReadRepository<T> {
}
Now your specific domain repos can extend from DataRepository:
@Repository
public interface UserRepository extends DataRepository<UserEntity> {
}
QueryableReadRepositoryImpl:
@Transactional
public class QueryableReadRepositoryImpl<T> extends QuerydslJpaPredicateExecutor<T>
implements QueryableReadRepository<T> {
private static final EntityPathResolver resolver = SimpleEntityPathResolver.INSTANCE;
private final EntityPath<T> path;
private final PathBuilder<T> builder;
private final Querydsl querydsl;
public QueryableReadRepositoryImpl(JpaEntityInformation<T, ?> entityInformation,
EntityManager entityManager) {
super(entityInformation, entityManager, resolver, null);
this.path = resolver.createPath(entityInformation.getJavaType());
this.builder = new PathBuilder<T>(path.getType(), path.getMetadata());
this.querydsl = new Querydsl(entityManager, builder);
}
@Override
public Optional<T> findOne(Predicate predicate) {
return super.findOne(predicate);
}
@Override
public List<T> findAll(OrderSpecifier<?>... orders) {
return super.findAll(orders);
}
@Override
public List<T> findAll(Predicate predicate, Sort sort) {
return executeSorted(createQuery(predicate).select(path), sort);
}
@Override
public Page<T> findAll(Predicate predicate, Pageable pageable) {
return super.findAll(predicate, pageable);
}
@Override
public List<T> findAll(Predicate predicate) {
return super.findAll(predicate);
}
public List<T> findAll(Sort sort) {
return executeSorted(createQuery().select(path), sort);
}
@Override
public Page<T> findAll(Pageable pageable) {
final JPQLQuery<?> countQuery = createCountQuery();
JPQLQuery<T> query = querydsl.applyPagination(pageable, createQuery().select(path));
return PageableExecutionUtils.getPage(
query.distinct().fetch(),
pageable,
countQuery::fetchCount);
}
private List<T> executeSorted(JPQLQuery<T> query, Sort sort) {
return querydsl.applySorting(sort, query).distinct().fetch();
}
}
CustomRepositoryFactoryBean:
public class CustomRepositoryFactoryBean<T extends Repository<S, I>, S, I>
extends JpaRepositoryFactoryBean<T, S, I> {
public CustomRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
super(repositoryInterface);
}
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new CustomRepositoryFactory(entityManager);
}
CustomRepositoryFactory:
public class CustomRepositoryFactory extends JpaRepositoryFactory {
private final EntityManager entityManager;
public CustomRepositoryFactory(EntityManager entityManager) {
super(entityManager);
this.entityManager = entityManager;
}
@Override
protected RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) {
RepositoryFragments fragments = super.getRepositoryFragments(metadata);
if (QueryableReadRepository.class.isAssignableFrom(
metadata.getRepositoryInterface())) {
JpaEntityInformation<?, Serializable> entityInformation =
getEntityInformation(metadata.getDomainType());
Object queryableFragment = getTargetRepositoryViaReflection(
QueryableReadRepositoryImpl.class, entityInformation, entityManager);
fragments = fragments.append(RepositoryFragment.implemented(queryableFragment));
}
return fragments;
}
Main class:
@EnableJpaRepositories(repositoryFactoryBeanClass = CustomRepositoryFactoryBean.class)
public class App {
}
This has the advantage that you provide only one (fragment) implementation for a custom repo. The base repository implementation is still Spring's default implementation. The example provided a new repo but you can probably also just override the default implementation of QuerydslPredicateExecutor
in CustomRepositoryFactory
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<T> implements QuerydslPredicateExecutor<T> {
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<T> findOne(Predicate predicate) {
return querydslPredicateExecutor.findOne(predicate);
}
@Override
public List<T> findAll(Predicate predicate) {
return querydslPredicateExecutor.findAll(predicate);
}
@Override
public List<T> findAll(Predicate predicate, Sort sort) {
return querydslPredicateExecutor.findAll(predicate, applyDefaultOrder(sort));
}
@Override
public List<T> findAll(Predicate predicate, OrderSpecifier<?>... orders) {
return querydslPredicateExecutor.findAll(predicate, orders);
}
@Override
public List<T> findAll(OrderSpecifier<?>... orders) {
return querydslPredicateExecutor.findAll(orders);
}
@Override
public Page<T> 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<T extends Repository<S, I>, S, I> extends JpaRepositoryFactoryBean<T, S, I> {
/**
* 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);
}
}