Замена устаревшего QuerydslJpaRepository на QuerydslJpaPredicateExecutor завершается ошибкой - PullRequest
0 голосов
/ 31 октября 2018

Мне потребовалось несколько пользовательских методов запросов с поддержкой QueryDSL, и я следовал на этот SO-ответ .

Это прекрасно работало, но после обновления до Spring Boot 2.1 (который обновляет Spring Data) я обнаружил, что QuerydslJpaRepository устарела. Простая замена на QuerydslJpaPredicateExecutor - которую мне подсказывает использовать документация - приводит к ошибке:

Вызывается: java.lang.IllegalArgumentException: объект класса [... ProjectingQueryDslJpaRepositoryImpl] должен быть экземпляром интерфейс org.springframework.data.jpa.repository.support.JpaRepositoryImplementation

... но реализация JpaRepositoryImplementation будет означать, что я должен реализовать все стандартные методы CRUD, которые я явно не хочу. Поэтому, если я удаляю конфигурацию repositoryBaseClass из @EnableJpaRepositories, чтобы обработать это как фрагмент хранилища с реализацией, он попытается создать экземпляр фрагмента, даже если он помечен @NoRepositoryBean, что дает мне ошибку:

Причина: java.lang.IllegalArgumentException: не удалось создать запрос для метода public abstract 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);
        }
    }
}

Ответы [ 3 ]

0 голосов
/ 15 февраля 2019

Этот тестовый пример имеет более чистую версию выполнения запросов с использованием 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", "dave@matthews.com"));
        carter = repository.save(new User("Carter", "Beauford", "carter@beauford.com"));
        oliver = repository.save(new User("Oliver", "matthews", "oliver@matthews.com"));
        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);
0 голосов
/ 16 июля 2019

В Spring Data JPA 2.1.6 конструктор QuerydslJpaPredicateExecutor изменился. Я представляю здесь альтернативный подход с использованием оболочки для https://stackoverflow.com/a/53960209/3351474.. Это делает решение независимым от внутренних компонентов Spring Data JPA. 3 класса должны быть реализованы.

В качестве примера я беру здесь настраиваемую реализацию Querydsl, в которой в качестве критерия сортировки всегда используется creationDate объекта, если ничего не передано. В этом примере я предполагаю, что этот столбец существует в некотором @MappedSuperClass для всех сущностей. Используйте сгенерированные статические метаданные в реальной жизни вместо жестко закодированной строки creationDate.

В качестве первого обернутого делегирования все CustomQuerydslJpaRepositoryIml делегирования всех методов в 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);
    }
}

Затем CustomJpaRepositoryFactory делает магию и предоставляет класс-оболочку Querydsl вместо класса по умолчанию. Значение по умолчанию передается в качестве параметра и переносится.

/**
 * 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];
    }
}

Наконец CustomJpaRepositoryFactoryBean. Это должно быть зарегистрировано в приложении Spring Boot, чтобы Spring знал, где можно получить реализации репозитория, например, с:

@SpringBootApplication
@EnableJpaRepositories(basePackages = "your.package",
        repositoryFactoryBeanClass = CustomJpaRepositoryFactoryBean.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);
    }
}
0 голосов
/ 28 декабря 2018

С Spring Boot 2.1.1 вам может помочь следующее решение. Ключ должен расширить JpaRepositoryFactory и переопределить метод getRepositoryFragments(RepositoryMetadata metadata). В этом методе вы можете предоставить базовые (или более конкретные фрагменты) реализации для любого настраиваемого хранилища, которое следует использовать для каждого расширяющегося хранилища.

Позвольте мне показать вам пример:

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

Следующий интерфейс объединяет различные репозитории.

DataRepository:

@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 по умолчанию. В примере предоставлено новое хранилище, но вы также можете просто переопределить реализацию по умолчанию QuerydslPredicateExecutor в CustomRepositoryFactory

...