草庐IT

java - QueryDsl 对 Map 字段键的 Web 查询

coder 2023-05-11 原文

概述

给定

  • Spring Data JPA、Spring Data Rest、QueryDsl
  • 一个 Meetup实体
    • Map<String,String> properties field
      • 坚持 MEETUP_PROPERTY表作为 @ElementCollection
  • 一个 MeetupRepository
    • 扩展 QueryDslPredicateExecutor<Meetup>

我希望

网络查询

GET /api/meetup?properties[aKey]=aValue

仅返回具有指定键和值的属性条目的 Meetup:aKey=aValue。

但是,这对我不起作用。 我错过了什么?

试过

简单字段

使用简单的字段,例如名称和描述:

GET /api/meetup?name=whatever

收集字段像参与者一样工作:

GET /api/meetup?participants.name=whatever

但不是这个 Map 字段。

自定义 QueryDsl 绑定(bind)

我尝试通过拥有存储库来自定义绑定(bind)

extend QuerydslBinderCustomizer<QMeetup>

并覆盖

customize(QuerydslBindings bindings, QMeetup meetup)

方法,但是 customize()方法被命中,lambda 内部的绑定(bind)代码没有。

编辑:得知这是因为 QuerydslBindings评估查询参数的方法不要让它与 pathSpecs 匹配映射它在内部持有 - 其中包含您的自定义绑定(bind)。

一些细节

Meetup.properties 字段

@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "MEETUP_PROPERTY", joinColumns = @JoinColumn(name = "MEETUP_ID"))
@MapKeyColumn(name = "KEY")
@Column(name = "VALUE", length = 2048)
private Map<String, String> properties = new HashMap<>();

自定义 querydsl 绑定(bind)

编辑:见上文;事实证明,这对我的代码没有任何作用。

public interface MeetupRepository extends PagingAndSortingRepository<Meetup, Long>,
                                          QueryDslPredicateExecutor<Meetup>,
                                          QuerydslBinderCustomizer<QMeetup> {

    @Override
    default void customize(QuerydslBindings bindings, QMeetup meetup) {
        bindings.bind(meetup.properties).first((path, value) -> {
            BooleanBuilder builder = new BooleanBuilder();
            for (String key : value.keySet()) {
                builder.and(path.containsKey(key).and(path.get(key).eq(value.get(key))));
            }
            return builder;
        });
}

其他发现

  1. QuerydslPredicateBuilder.getPredicate()QuerydslBindings.getPropertyPath()尝试 2 种方法来返回路径,以便它可以做出 QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver.postProcess() 的谓词可以使用。
    • 1 是查看自定义绑定(bind)。我没有看到任何表达 map 查询的方法
    • 2 默认为 Spring 的 bean 路径。那里有同样的表达问题。你如何表达一张 map ? 所以看起来不可能得到QuerydslPredicateBuilder.getPredicate()自动创建谓词。 很好 - 我可以手动完成,如果我可以连接到 QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver.postProcess()

如何覆盖该类或替换 bean?它被实例化并作为 RepositoryRestMvcConfiguration.repoRequestArgumentResolver() 中的 bean 返回bean 声明。

  1. 可以通过声明我自己的 repoRequestArgumentResolver 来覆盖该 bean bean,但它不会被使用。
    • 它被 RepositoryRestMvcConfiguration 覆盖s。我不能通过设置它来强制@Primary@Ordered(HIGHEST_PRECEDENCE) .
    • 可以通过显式组件扫描 RepositoryRestMvcConfiguration.class 来强制它,但这也会弄乱 Spring Boot 的自动配置,因为它会导致 RepositoryRestMvcConfiguration's要处理的 bean 声明 在任何自动配置运行之前。除其他外,这会导致 Jackson 以不需要的方式序列化响应。

问题

嗯 - 看起来我期望的支持不存在。

所以问题变成了: 如何正确覆盖 repoRequestArgumentResolver bean ?

顺便说一句 - QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver尴尬地不公开。 :/

最佳答案

更换 Bean

实现 ApplicationContextAware

这就是我在应用程序上下文中替换 bean 的方式。

感觉有点老套。我很想听听一个更好的方法来做到这一点。

@Configuration
public class CustomQuerydslHandlerMethodArgumentResolverConfig implements ApplicationContextAware {

    /**
     * This class is originally the class that instantiated QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver and placed it into the Spring Application Context
     * as a {@link RootResourceInformationHandlerMethodArgumentResolver} by the name of 'repoRequestArgumentResolver'.<br/>
     * By injecting this bean, we can let {@link #meetupApiRepoRequestArgumentResolver} delegate as much as possible to the original code in that bean.
     */
    private final RepositoryRestMvcConfiguration repositoryRestMvcConfiguration;

    @Autowired
    public CustomQuerydslHandlerMethodArgumentResolverConfig(RepositoryRestMvcConfiguration repositoryRestMvcConfiguration) {
        this.repositoryRestMvcConfiguration = repositoryRestMvcConfiguration;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) ((GenericApplicationContext) applicationContext).getBeanFactory();
        beanFactory.destroySingleton(REPO_REQUEST_ARGUMENT_RESOLVER_BEAN_NAME);
        beanFactory.registerSingleton(REPO_REQUEST_ARGUMENT_RESOLVER_BEAN_NAME,
                                      meetupApiRepoRequestArgumentResolver(applicationContext, repositoryRestMvcConfiguration));
    }

    /**
     * This code is mostly copied from {@link RepositoryRestMvcConfiguration#repoRequestArgumentResolver()}, except the if clause checking if the QueryDsl library is
     * present has been removed, since we're counting on it anyway.<br/>
     * That means that if that code changes in the future, we're going to need to alter this code... :/
     */
    @Bean
    public RootResourceInformationHandlerMethodArgumentResolver meetupApiRepoRequestArgumentResolver(ApplicationContext applicationContext,
                                                                                                     RepositoryRestMvcConfiguration repositoryRestMvcConfiguration) {
        QuerydslBindingsFactory factory = applicationContext.getBean(QuerydslBindingsFactory.class);
        QuerydslPredicateBuilder predicateBuilder = new QuerydslPredicateBuilder(repositoryRestMvcConfiguration.defaultConversionService(),
                                                                                 factory.getEntityPathResolver());

        return new CustomQuerydslHandlerMethodArgumentResolver(repositoryRestMvcConfiguration.repositories(),
                                                               repositoryRestMvcConfiguration.repositoryInvokerFactory(repositoryRestMvcConfiguration.defaultConversionService()),
                                                               repositoryRestMvcConfiguration.resourceMetadataHandlerMethodArgumentResolver(),
                                                               predicateBuilder, factory);
    }
}

从 http 参数创建 map 搜索谓词

扩展 RootResourceInformationHandlerMethodArgumentResolver

这些是基于 http 查询参数创建我自己的 map 搜索谓词的代码片段。 再次 - 很想知道更好的方法。

postProcess 方法调用:

        predicate = addCustomMapPredicates(parameterMap, predicate, domainType).getValue();

就在之前将 predicate 引用传递到 QuerydslRepositoryInvokerAdapter 构造函数并返回。

这是 addCustomMapPredicates 方法:

    private BooleanBuilder addCustomMapPredicates(MultiValueMap<String, String> parameters, Predicate predicate, Class<?> domainType) {
        BooleanBuilder booleanBuilder = new BooleanBuilder();
        parameters.keySet()
                  .stream()
                  .filter(s -> s.contains("[") && matches(s) && s.endsWith("]"))
                  .collect(Collectors.toList())
                  .forEach(paramKey -> {
                      String property = paramKey.substring(0, paramKey.indexOf("["));
                      if (ReflectionUtils.findField(domainType, property) == null) {
                          LOGGER.warn("Skipping predicate matching on [%s]. It is not a known field on domainType %s", property, domainType.getName());
                          return;
                      }
                      String key = paramKey.substring(paramKey.indexOf("[") + 1, paramKey.indexOf("]"));
                      parameters.get(paramKey).forEach(value -> {
                          if (!StringUtils.hasLength(value)) {
                              booleanBuilder.or(matchesProperty(key, null));
                          } else {
                              booleanBuilder.or(matchesProperty(key, value));
                          }
                      });
                  });
        return booleanBuilder.and(predicate);
    }

    static boolean matches(String key) {
        return PATTERN.matcher(key).matches();
    }

还有图案:

    /**
     * disallow a . or ] from preceding a [
     */
    private static final Pattern PATTERN = Pattern.compile(".*[^.]\\[.*[^\\[]");

关于java - QueryDsl 对 Map 字段键的 Web 查询,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45989336/

有关java - QueryDsl 对 Map 字段键的 Web 查询的更多相关文章

  1. ruby - ECONNRESET (Whois::ConnectionError) - 尝试在 Ruby 中查询 Whois 时出错 - 2

    我正在用Ruby编写一个简单的程序来检查域列表是否被占用。基本上它循环遍历列表,并使用以下函数进行检查。require'rubygems'require'whois'defcheck_domain(domain)c=Whois::Client.newc.query("google.com").available?end程序不断出错(即使我在google.com中进行硬编码),并打印以下消息。鉴于该程序非常简单,我已经没有什么想法了-有什么建议吗?/Library/Ruby/Gems/1.8/gems/whois-2.0.2/lib/whois/server/adapters/base.

  2. ruby-on-rails - 如何验证非模型(甚至非对象)字段 - 2

    我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss

  3. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

  4. java - 等价于 Java 中的 Ruby Hash - 2

    我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

  5. ruby - 如果指定键的值在数组中相同,如何合并哈希 - 2

    我有一个这样的哈希数组:[{:foo=>2,:date=>Sat,01Sep2014},{:foo2=>2,:date=>Sat,02Sep2014},{:foo3=>3,:date=>Sat,01Sep2014},{:foo4=>4,:date=>Sat,03Sep2014},{:foo5=>5,:date=>Sat,02Sep2014}]如果:date相同,我想合并哈希值。我对上面数组的期望是:[{:foo=>2,:foo3=>3,:date=>Sat,01Sep2014},{:foo2=>2,:foo5=>5:date=>Sat,02Sep2014},{:foo4=>4,:dat

  6. ruby-on-rails - 在 Rails 和 ActiveRecord 中查询时忽略某些字段 - 2

    我知道我可以指定某些字段来使用pluck查询数据库。ids=Item.where('due_at但是我想知道,是否有一种方法可以指定我想避免从数据库查询的某些字段。某种反拔?posts=Post.where(published:true).do_not_lookup(:enormous_field) 最佳答案 Model#attribute_names应该返回列/属性数组。您可以排除其中一些并传递给pluck或select方法。像这样:posts=Post.where(published:true).select(Post.attr

  7. ruby - 检查字符串是否包含散列中的任何键并返回它包含的键的值 - 2

    我有一个包含多个键的散列和一个字符串,该字符串不包含散列中的任何键或包含一个键。h={"k1"=>"v1","k2"=>"v2","k3"=>"v3"}s="thisisanexamplestringthatmightoccurwithakeysomewhereinthestringk1(withspecialcharacterslike(^&*$#@!^&&*))"检查s是否包含h中的任何键的最佳方法是什么,如果包含,则返回它包含的键的值?例如,对于上面的h和s的例子,输出应该是v1。编辑:只有字符串是用户定义的。哈希将始终相同。 最佳答案

  8. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

  9. java - 我的模型类或其他类中应该有逻辑吗 - 2

    我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我

  10. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

随机推荐