【问题标题】:How can you use different operators with QueryDSL Web?您如何在 QueryDSL Web 中使用不同的运算符?
【发布时间】:2018-06-30 20:57:54
【问题描述】:

我在我的 Spring Boot 项目中使用 QueryDSL 并计划使用 Spring 的 Web 支持 (current query dsl web docs)。问题是,我找不到任何关于使用不同运算符的信息。如何定义not equalsmatches regex 操作?乍一看,它所做的只是将您的?fieldname=value 格式GET 请求转换为您在存储库中设置的预定义操作。我可以扩展它以允许对同一字段进行多次操作吗?

示例:

目前我可以通过传递 URL 参数获得 QueryDsl Predicate,例如 ?user.company.id=1

@Controller
class UserController {

  @Autowired UserRepository repository;

  @RequestMapping(value = "/", method = RequestMethod.GET)
  Page<User> getUsers(@QuerydslPredicate(root = User.class) Predicate predicate,    
          Pageable pageable) {

    return repository.findAll(predicate, pageable);
  }
}

但正如我链接的文档所述,我只能为某个字段定义一个操作。如果我想要用户,其中user.lastName 以某些内容开头并且仍然保留查询精确匹配的可能性? (?lastName=Xyz,contains?lastName=Xyz,equals 可能)

QuerydslBinderCustomizer 定义了每个字段的操作,但您只能定义如何处理该特定字段,不可能添加多个操作。

也许我不能用 QueryDSL 做到这一点,但是通常在 Spring Boot 中你如何将过滤器应用于搜索查询?

【问题讨论】:

标签: spring spring-boot spring-data-jpa querydsl


【解决方案1】:

我正在做类似的事情。虽然当我尝试做更复杂的动作时遇到了一些限制。我在一些步骤中做了什么:

  • 创建一个扩展 QuerydslBinderCustomizer&lt;QUser&gt; 的新接口 MyBinderCustomizer&lt;T extends EntityPath&lt;?&gt;&gt;(注意 User 的 Q,您需要 QueryDSL 自动生成的类而不是您的实体)。
  • 实现customize 方法。例如:

@Override
public default void customize(QuerydslBindings bindings, T root) {
    bindings.bind(String.class).all(MyBinderCustomizer::applyStringComparison);
}

static BooleanExpression applyStringComparison(Path<String> path, Collection<? extends String> strings) {
    BooleanExpression result = null;

    for (String s : strings) {
        try {
            final String[] parts = s.split(",");
            final String operator = parts[0];
            final String value = parts.length > 1 ? parts[1] : null;

            final Method method = Arrays.stream(path.getClass().getMethods())
                    .filter(m -> operator.equals(m.getName()))
                    .filter(m -> BooleanExpression.class.equals(m.getReturnType()))
                    .filter(m -> m.getParameterTypes().length == (value == null ? 0 : 1))
                    .filter(m -> value == null || m.getParameterTypes()[0].equals(String.class) || m.getParameterTypes()[0].equals(Object.class))
                    .findFirst().get();

            final BooleanExpression be;
            if (value == null) {
                be = (BooleanExpression) method.invoke(path);
            } else {
                be = (BooleanExpression) method.invoke(path, value);
            }

            result = result == null ? be : result.and(be);

        } catch (Throwable t) {
            throw new RuntimeException(t);
        }
    }

    return result;

}
  • 请注意,您应该更改值/运算符顺序,以便可以调用 isNull 等无值运算符。
  • 您的存储库必须扩展 MyBinderCustomizer&lt;QUser&gt;(再次注意 Q)。

这将允许您使用这些操作:

公共 BooleanExpression StringExpression.like(java.lang.String) 公共 BooleanExpression StringExpression.notLike(java.lang.String) 公共 BooleanExpression StringExpression.notEqualsIgnoreCase(java.lang.String) 公共 BooleanExpression StringExpression.containsIgnoreCase(java.lang.String) public BooleanExpression StringExpression.likeIgnoreCase(java.lang.String) 公共 BooleanExpression StringExpression.startsWithIgnoreCase(java.lang.String) 公共 BooleanExpression StringExpression.endsWithIgnoreCase(java.lang.String) 公共 BooleanExpression StringExpression.equalsIgnoreCase(java.lang.String) 公共 BooleanExpression StringExpression.startsWith(java.lang.String) 公共 BooleanExpression StringExpression.endsWith(java.lang.String) 公共 BooleanExpression StringExpression.matches(java.lang.String) 公共 BooleanExpression StringExpression.contains(java.lang.String) 公共 BooleanExpression StringExpression.isEmpty() 公共 BooleanExpression StringExpression.isNotEmpty() 公共布尔表达式 SimpleExpression.isNull() 公共布尔表达式 SimpleExpression.isNotNull() 公共布尔表达式 SimpleExpression.ne(java.lang.Object) 公共布尔表达式 SimpleExpression.eq(java.lang.Object)

【讨论】:

  • 这适用于字符串字段。如何为数字字段实现相同的功能?解析器抛出异常 ...&id=gt,100
  • @Ronen ,我现在没有任何相关的代码可以玩,但是,你能不能把“gt,100”字符串,分成两个字符串{“gt”,“100”}然后只解析第二个寻找数字?
【解决方案2】:

Spring Data QueryDSL Value Operators 库扩展了Spring Data QueryDSL web support 的运算符,不仅适用于字符串字段,还适用于数字和枚举字段。它需要一些特殊配置才能使其适用于非字符串字段,如here 所述:

值运算符在基于字符串的属性/字段上无缝工作。但是,这些运算符不适用于像 Number 或 Enum 这样的非字符串值,因为默认情况下解析注释 QuerydslPredicateQuerydslPredicateArgumentResolver 用于在 RESTful 方法(又名 RestController 方法)上注释搜索处理方法,执行强类型根据 Querydsl 的指导设计原则,即尝试将从 HTTP 请求接收到的值转换为相应 Q-Classes 中定义的确切类型。这在没有值运算符的情况下运行良好,并且符合 Querydsl 允许类型安全查询的承诺,但阻碍了值运算符发挥作用的路径。

该库提供了两种方法使运算符适用于非字符串字段:

  1. 从查询参数中提取运算符的过滤器,因此查询参数仍然可以转换为相应的类型(使用强类型)
  2. 替换 QuerydslPredicateArgumentResolver 中的 ConversionService,因此所有查询参数都被视为字符串(失去强类型)

这两种方法都有很好的文档记录,以及它们的用例和缺点。

我目前正在评估方法 1,因为这适合我们的用例,但我需要扩展它以适应 DateTime 字段和一些自定义运算符。

【讨论】:

  • 有日期运算符的例子吗?
  • 该库的 4.0.0 版本已发布,它也处理日期(时间)字段。例如,您可以使用以下查询字符串:/users/search?profile.dateOfBirth=lt(1/1/1970)&amp;profile.dateOfBirth=and(gt(12/31/1959))。有关更多示例,请参阅UserSearchIT test
【解决方案3】:

https://bitbucket.org/gt_tech/spring-data-querydsl-value-operators/src/master/

这里的文档说:

QuerydslPredicateArgumentResolver 使用 ConversionService 进行类型转换。由于将 String 转换为 Enum 或 String 转换为 Integer 是 Spring 依赖注入的核心,因此不建议更改那些默认的内置转换器(永远不要这样做)。该库提供了 BeanPostProcessor 和 ServletFilter 的实验性组合,可以在目标应用程序的上下文中显式配置,以禁用 QuerydslPredicateArgumentResolver 尝试的强类型转换。

因此,要实现这一点,您需要将其添加到应用程序上下文中:

/**
 * Note the use of delegate ConversionService which comes handy for types like
 * java.util.Date for handling powerful searches natively with Spring data.
 * @param factory QuerydslBindingsFactory instance
 * @param conversionServiceDelegate delegate ConversionService
 * @return
 */
@Bean
public QuerydslPredicateArgumentResolverBeanPostProcessor querydslPredicateArgumentResolverBeanPostProcessor(
        QuerydslBindingsFactory factory, DefaultFormattingConversionService conversionServiceDelegate) {
    return new QuerydslPredicateArgumentResolverBeanPostProcessor(factory, conversionServiceDelegate);
}

如果有人成功实现了这个实验性功能,请告诉我。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-04-30
    • 1970-01-01
    • 2021-01-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-27
    相关资源
    最近更新 更多