J'avais besoin de méthodes de requête personnalisées activées par QueryDSL et suivais cette SO réponse .
Cela a très bien fonctionné, mais après la mise à niveau vers Spring Boot 2.1 (qui met à niveau Spring Data), j'ai constaté que QuerydslJpaRepository
était obsolète . Le remplacer simplement par QuerydslJpaPredicateExecutor
- que la documentation me dit d'utiliser - conduit à une erreur:
Causé par: Java.lang.IllegalArgumentException: objet de la classe [... ProjectingQueryDslJpaRepositoryImpl] doit être une instance de interface org.springframework.data.jpa.repository.support.JpaRepositoryImplementation
... mais implémenter JpaRepositoryImplementation
signifierait que je dois implémenter toutes les méthodes CRUD standard, ce que je ne souhaite évidemment pas . Donc, si je supprime la configuration repositoryBaseClass
de @EnableJpaRepositories
, je traiterai cela comme un fragment de référentiel avec implémentation , il essaiera d’instancier le fragment, même s’il est marqué avec @NoRepositoryBean
, ce qui me donne l’erreur suivante:
Causée par: Java.lang.IllegalArgumentException: Echec de la création de la requête for method public abstract Java.util.Optional ProjectingQueryDslJpaRepository.findOneProjectedBy (com.querydsl.core.types.Expression, com.querydsl.core.types.Predicate)!. Au moins 1 paramètre (s) fourni (s) mais seulement 0 paramètre (s) présent (s) dans question.
...
Causée par: Java.lang.IllegalArgumentException: au moins 1 paramètre (s) fourni mais seulement 0 paramètre (s) présent dans la requête.
Version abrégée de la 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);
}
}
}
Avec Spring Boot 2.1.1 la solution suivante peut vous aider. La clé consiste à étendre JpaRepositoryFactory
et à remplacer la méthode getRepositoryFragments(RepositoryMetadata metadata)
. Dans cette méthode, vous pouvez fournir des implémentations de base (ou de fragments plus spécifiques) pour tout référentiel personnalisé devant être utilisé pour chaque référentiel étendu.
Permettez-moi de vous montrer un exemple:
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);
}
L’interface suivante combine différents référentiels.
Dépôt de données:
@NoRepositoryBean
public interface DataRepository<T>
extends CrudRepository<T, String>, QueryableReadRepository<T> {
}
Désormais, vos dépôts de domaine spécifiques peuvent s’étendre à partir de 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;
}
Classe principale:
@EnableJpaRepositories(repositoryFactoryBeanClass = CustomRepositoryFactoryBean.class)
public class App {
}
Cela présente l'avantage que vous ne fournissez qu'une seule implémentation (fragment) pour un référentiel personnalisé. L'implémentation du référentiel de base est toujours l'implémentation par défaut de Spring. L'exemple fourni un nouveau référentiel, mais vous pouvez probablement aussi simplement remplacer l'implémentation par défaut de QuerydslPredicateExecutor
dans CustomRepositoryFactory
Ce scénario de test a une version plus propre des requêtes en cours d'exécution à l'aide de querydsl
JpaEntityInformation<User, Integer> information = new JpaMetamodelEntityInformation<>(User.class,
em.getMetamodel());
SimpleJpaRepository<User, Integer> repository = new SimpleJpaRepository<>(information, em);
dave = repository.save(new User("Dave", "Matthews", "[email protected]"));
carter = repository.save(new User("Carter", "Beauford", "[email protected]"));
oliver = repository.save(new User("Oliver", "matthews", "[email protected]"));
adminRole = em.merge(new Role("admin"));
this.predicateExecutor = new QuerydslJpaPredicateExecutor<>(information, em, SimpleEntityPathResolver.INSTANCE, null);
BooleanExpression isCalledDave = user.firstname.eq("Dave");
BooleanExpression isBeauford = user.lastname.eq("Beauford");
List<User> result = predicateExecutor.findAll(isCalledDave.or(isBeauford));
assertThat(result).containsExactlyInAnyOrder(carter, dave);