用 QuerydslJpaPredicateExecutor 替换已弃用的 QuerydslJpaRepository 失败
Posted
技术标签:
【中文标题】用 QuerydslJpaPredicateExecutor 替换已弃用的 QuerydslJpaRepository 失败【英文标题】:Replacing deprecated QuerydslJpaRepository with QuerydslJpaPredicateExecutor fails 【发布时间】:2019-04-04 14:25:48 【问题描述】:我需要一些启用 QueryDSL 的自定义查询方法并遵循 this SO answer。
效果很好,但在升级到 Spring Boot 2.1(升级 Spring Data)后,我发现 QuerydslJpaRepository
已被弃用。
只需将其替换为 QuerydslJpaPredicateExecutor
- documentation 告诉我使用 - 会导致错误:
原因:java.lang.IllegalArgumentException:类对象 [...ProjectingQueryDslJpaRepositoryImpl] 必须是 界面 org.springframework.data.jpa.repository.support.JpaRepositoryImplementation
...但是实现JpaRepositoryImplementation
意味着我必须实现所有标准的CRUD 方法,这显然是我不想要的。
因此,如果我从@EnableJpaRepositories
中删除repositoryBaseClass
配置,将其视为具有实现的存储库片段,它将尝试实例化片段,即使它标记为@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);
【问题讨论】:
你找到解决办法了吗? 很遗憾没有。 【参考方案1】:使用 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);
以下界面结合了不同的存储库。
数据存储库:
@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 的默认实现。该示例提供了一个新的存储库,但您也可以在CustomRepositoryFactory
中覆盖QuerydslPredicateExecutor
的默认实现
【讨论】:
你能告诉我OrderSpecifier
是什么吗?它的导入语句是什么?【参考方案2】:
在 Spring Data JPA 2.1.6 中,QuerydslJpaPredicateExecutor
的构造函数已更改。
我在这里提出了一种使用https://***.com/a/53960209/3351474 包装器的替代方法。这使得解决方案独立于 Spring Data JPA 的内部。必须实现三个类。
作为一个例子,我在这里使用一个自定义的 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);
【讨论】:
【参考方案3】:这个测试用例有一个更简洁的使用 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);
【讨论】:
以上是关于用 QuerydslJpaPredicateExecutor 替换已弃用的 QuerydslJpaRepository 失败的主要内容,如果未能解决你的问题,请参考以下文章