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 Data), I've found that QuerydslJpaRepository
has been deprecated.
Simply replacing it with QuerydslJpaPredicateExecutor
- which the documentation tells me to use - leads to an error:
Caused by: java.lang.IllegalArgumentException: Object of class [...ProjectingQueryDslJpaRepositoryImpl] must be an instance of interface org.springframework.data.jpa.repository.support.JpaRepositoryImplementation
...but implementing JpaRepositoryImplementation
would mean that I have to implement all the standard CRUD methods, which I obviously don't want.
So if I remove the repositoryBaseClass
config from @EnableJpaRepositories
, to treat this just like a repository fragment with implementation, it will try to instantiate the fragment, even though it is marked with @NoRepositoryBean
, giving me the error:
Caused by: java.lang.IllegalArgumentException: Failed to create query for method public abstract java.util.Optional ProjectingQueryDslJpaRepository.findOneProjectedBy(com.querydsl.core.types.Expression,com.querydsl.core.types.Predicate)! At least 1 parameter(s) provided but only 0 parameter(s) present in query.
...
Caused by: java.lang.IllegalArgumentException: At least 1 parameter(s) provided but only 0 parameter(s) present in query.
Abriged version of source:
@Configuration
@EnableJpaRepositories(basePackageClasses = Application.class, repositoryBaseClass = ProjectingQueryDslJpaRepositoryImpl.class)
@EnableTransactionManagement
@EnableJpaAuditing
@RequiredArgsConstructor(onConstructor = @__({@Autowired}))
public class DatabaseConfig {}
_
@NoRepositoryBean
public interface ProjectingQueryDslJpaRepository<T> extends QuerydslBinderCustomizer<EntityPath<T>>, QuerydslPredicateExecutor<T> {
@NonNull
<P> Page<P> findPageProjectedBy(@NonNull Expression<P> factoryExpression, Predicate predicate,
@NonNull Pageable pageable);
@NonNull
<P> Optional<P> findOneProjectedBy(@NonNull Expression<P> factoryExpression, @NonNull Predicate predicate);
@Override
default void customize(@NonNull QuerydslBindings bindings, @NonNull EntityPath<T> root){
bindings.bind(String.class).first((SingleValueBinding<StringPath, String>) StringExpression::containsIgnoreCase);
}
}
_
public class ProjectingQueryDslJpaRepositoryImpl<T, ID extends Serializable> extends QuerydslJpaRepository<T, ID>
implements ProjectingQueryDslJpaRepository<T> {
private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE;
private final EntityPath<T> path;
private final Querydsl querydsl;
public ProjectingQueryDslJpaRepositoryImpl(@NonNull JpaEntityInformation<T, ID> entityInformation, @NonNull EntityManager entityManager) {
this(entityInformation, entityManager, DEFAULT_ENTITY_PATH_RESOLVER);
}
public ProjectingQueryDslJpaRepositoryImpl(@NonNull JpaEntityInformation<T, ID> entityInformation, @NonNull EntityManager entityManager,
@NonNull EntityPathResolver resolver) {
super(entityInformation, entityManager, resolver);
this.path = resolver.createPath(entityInformation.getJavaType());
PathBuilder<T> builder = new PathBuilder<>(path.getType(), path.getMetadata());
this.querydsl = new Querydsl(entityManager, builder);
}
@Override
public <P> Page<P> findPageProjectedBy(@NonNull Expression<P> factoryExpression, Predicate predicate,
@NonNull Pageable pageable) {
final JPQLQuery<?> countQuery = createCountQuery(predicate);
JPQLQuery<P> query = querydsl.applyPagination(pageable, createQuery(predicate).select(factoryExpression));
return PageableExecutionUtils.getPage(query.fetch(), pageable, countQuery::fetchCount);
}
@Override
public <P> Optional<P> findOneProjectedBy(@NonNull Expression<P> factoryExpression, @NonNull Predicate predicate) {
try {
return Optional.ofNullable(createQuery(predicate).select(factoryExpression).from(path).fetchOne());
} catch (NonUniqueResultException ex) {
throw new IncorrectResultSizeDataAccessException(ex.getMessage(), 1, ex);
}
}
}
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