Spring Data Web 对 QueryDsl 的支持在应用程序启动时偶尔起作用;定制器并不总是被调用

Spring Data Web Support for QueryDsl works sporadically upon application startup; customizer not always invoked

我的项目使用 Spring Boot 2.1.3, Java 8, QueryDsl 4.2.1.(成熟的关键项目,暂时无法将SB版本升级到最新的2.4.4。)

最近,我添加了Spring数据网络支持以简化绑定请求查询参数到QueryDsl使用的搜索谓词在我的 Spring Data JPA 存储库方法中 - 在 Web 控制器方法中使用 @QuerydslPredicate 注释。能够为特定属性或整个 classes 属性自定义绑定,这似乎工作得很好......直到我们发现搜索会随机停止工作。

经过一些调查,我注意到有时,在应用程序启动后,我的 Spring Data JPA 存储库上的 customize 方法实现在调用需要填充谓词的控制器方法。查询,如果它们有任何依赖于自定义绑定的搜索参数,将会失败。但这是疯狂的部分。 我会停止并重新启动应用程序(不做任何更改!),一切都会开始工作,同样的查询会产生预期的结果。

值得注意的是,在调试模式(在 IntelliJ IDEA 中)中重新启动似乎可以正确加载所有内容(大多数情况下,但我认为并非总是如此),并且“自定义”方法中的代码将被执行。我会多次停止并重新启动应用程序,而不更改任何内容或重建。有时它会起作用,有时它不会!同样query/request。该行为在不同环境中的不一致性是相同的。我已确保 QueryDSL APT 依赖项(用于 Q 类型的注释处理和代码生成)在范围 provided 中声明并且未打包到 JAR 中。

我看到 Spring QueryDsl 库始终包含在 class 路径中并可用,这应该确保自动启用对 @QuerydslPredicate 注释的支持 -根据文档。似乎 - 根据启动时的随机情况 - 框架没有找到所需域类型的定制器实现(我的实体 class。)

一旦成功,就成功了。但我永远无法确定一旦应用程序停止并重新启动它是否会启用。任何人都知道什么可能导致这种奇怪的行为?版本不匹配,也许? Spring Data Web 支持 QueryDsl 的错误?也许,我应该尝试使用不同的早期版本 spring querydsl 来配合我的 Spring Boot v2.1.3 吗?

同时我不得不在服务层中手动构建谓词,但如果能解开这个谜团就好了。

找到问题,以防有人遇到类似问题...

在我的控制器方法中,我最初省略了 @QuerydslPredicate 注释的 bindings 参数,让框架找到 QuerydslBinderCustomizer 实现。我假设框架会寻找并找到我的 Spring Data JPA 存储库实现:

  public interface MyEntityRepository
    extends JpaRepository<MyEntity, Integer>, QuerydslPredicateExecutor<MyEntity>, QuerydslBinderCustomizer<QMyEntity> {

    @Override
    default void customize(QuerydslBindings bindings, QMyEntity myEntity) {
        ... // my bindings customizations
}

不幸的是,对于同一实体 class,还有另一个 - 遗留 - 存储库接口。显然,第二个存储库具有不同的名称并且没有实现 QuerydslPredicateExecutor,我错误地认为这会使框架完全忽略它并始终找到正确的实现 - 基于上述接口。显然,Spring 不是这样做的,而且它可能是框架中的一个缺陷。以下是 Spring 的 QuerydslBindingsFactory:

的片段
    .orElseGet(() -> repositories.flatMap(it -> it.getRepositoryFor(domainType))//
                    .map(it -> it instanceof QuerydslBinderCustomizer ? (QuerydslBinderCustomizer<EntityPath<?>>) it : null)//
                    .orElse(NoOpCustomizer.INSTANCE));

因此,框架简单地获取给定域类型的 first repo 实例,如果它没有实现 QuerydslBinderCustomizer,则抛出毛巾,并给出再往上看。当然,最好每个实体类型只有一个 repo class。然而,在我们的例子中,遗留的 class 仍在其他地方使用,可能还没有被删除。此外,还有一些原因让我想在不同的 classes 中将这个和旧的实现分开。因此,在我的情况下,取决于首先找到哪个实例,事情会起作用或不起作用。通过在控制器方法注释的 bindings arg 中显式指定定制程序存储库 class,我解决了问题:

    @GetMapping("/xyz")
    public List<MyEntity> findMyEntitiesByPredicate(
        @QuerydslPredicate(root = MyEntity.class, bindings = MyEntityRepository.class) Predicate predicate, ...) {...}