一尘不染

用QuerydslJpaPredicateExecutor替换不推荐使用的QuerydslJpaRepository失败

spring-boot

我需要一些启用了QueryDSL的自定义查询方法,并遵循此SO答案。

效果很好,但是升级到Spring Boot 2.1(升级了Spring
Data)之后,我发现它QuerydslJpaRepository已被弃用。简单地将其替换为QuerydslJpaPredicateExecutor-文档会告诉我使用-
会导致错误:

引起原因:java.lang.IllegalArgumentException:类[…
ProjectingQueryDslJpaRepositoryImpl]的对象必须是接口org.springframework.data.jpa.repository.support.JpaRepositoryImplementation的实例。

…但是实施JpaRepositoryImplementation将意味着我必须实施所有标准的CRUD方法,这显然是我所不希望的。因此,如果我repositoryBaseClass从中删除config
@EnableJpaRepositories,以将其视为带有实现的存储库片段,即使它标有@NoRepositoryBean,它也会尝试实例化该片段,从而给我错误:

原因:java.lang.IllegalArgumentException:无法创建方法公共抽象java.util.Optional
ProjectingQueryDslJpaRepository.findOneProjectedBy(com.querydsl.core.types.Expression,com.querydsl.core.types.Predicate)的查询!提供至少1个参数,但查询中仅提供0个参数。

由以下原因引起:java.lang.IllegalArgumentException:提供了至少1个参数,但查询中仅存在0个参数。

原始版本:

@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);
        }
    }
}

阅读 2031

收藏
2020-05-30

共1个答案

一尘不染

使用 Spring Boot 2.1.1,以下解决方案可能会对您有所帮助。关键是扩展JpaRepositoryFactory和覆盖该方法getRepositoryFragments(RepositoryMetadatametadata)。在这种方法中,您可以为任何自定义存储库提供基本(或更具体的片段)实现,每个扩展存储库都应采用这些实现。

让我给你看一个例子:

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);
}

以下界面结合了不同的存储库。

资料储存库:

@NoRepositoryBean
public interface DataRepository<T>
    extends CrudRepository<T, String>, QueryableReadRepository<T> {
}

现在,您特定的域存储库可以从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;
  }

主班:

@EnableJpaRepositories(repositoryFactoryBeanClass = CustomRepositoryFactoryBean.class)
public class App {
}

这样做的好处是,您只能为自定义存储库提供一个(片段)实现。基本存储库实现仍然是Spring的默认实现。该示例提供了新的存储库,但您也可以覆盖QuerydslPredicateExecutorin的默认实现CustomRepositoryFactory

2020-05-30