Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replacing deprecated QuerydslJpaRepository with QuerydslJpaPredicateExecutor fails

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);
        }
    }
}
like image 550
Benjamin Maurer Avatar asked Oct 31 '18 12:10

Benjamin Maurer


3 Answers

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

like image 60
Vetemi Avatar answered Nov 20 '22 08:11

Vetemi


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);
    }
}
like image 3
k_o_ Avatar answered Nov 20 '22 07:11

k_o_


This test case has a cleaner version of running queries using querydsl

https://github.com/spring-projects/spring-data-jpa/blob/master/src/test/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutorUnitTests.java

        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);
like image 1
Ashok Koyi Avatar answered Nov 20 '22 06:11

Ashok Koyi