QueryDsl Web查询关于Map字段的键

总览

回答:

  • Spring Data JPA,Spring Data Rest,QueryDsl
  • 一个Meetup实体

    • Map<String,String> properties田野
    • 坚持作为一个MEETUP_PROPERTY@ElementCollection

  • 一个 MeetupRepository

    • 延伸 QueryDslPredicateExecutor<Meetup>

回答:

网络查询

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

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

但是,这对我不起作用。我想念什么?

试过了

简单字段

简单的字段可以工作,例如名称和描述:

GET /api/meetup?name=whatever

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

GET /api/meetup?participants.name=whatever

但不是此Map字段。

自定义QueryDsl绑定

我尝试通过拥有存储库来定制绑定

extend QuerydslBinderCustomizer<QMeetup>

并覆盖

customize(QuerydslBindings bindings, QMeetup meetup)

方法,但是当customize()方法被点击时,lambda内部的绑定代码却没有。

编辑:了解到这是因为QuerydslBindings评估查询参数的方法不允许它与pathSpecs它内部持有的地图相匹配-里面有您的自定义绑定。

一些细节

回答:

@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<>();

回答:

见上文;事实证明,这对我的代码无济于事。

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是查看自定义绑定。我在那里看不到任何表示地图查询的方法
    • 2是默认使用Spring的bean路径。那里同样表达问题。您如何表达地图?因此似乎无法QuerydslPredicateBuilder.getPredicate()自动创建谓词。很好-如果可以加入,我可以手动完成QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver.postProcess()

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

  1. 我 通过声明自己的repoRequestArgumentResolverbean 覆盖该bean ,但是不会被使用。

    • 它被RepositoryRestMvcConfigurations 覆盖。我无法通过设置它或来 它。@Primary``@Ordered(HIGHEST_PRECEDENCE)
    • 我 通过显式的组件扫描 强制执行此操作RepositoryRestMvcConfiguration.class,但这也会弄乱Spring Boot的自动配置,因为它会导致 RepositoryRestMvcConfiguration's在运行任何自动配置之前处理Bean声明。除其他外,这导致响应被杰克逊以不需要的方式序列化。

问题

好吧-好像我所期望的支持不存在。

所以问题就变成了: 我该 正确地覆盖repoRequestArgumentResolverbean?

BTW-

QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver尴尬的非公开。:/

回答:

更换豆

实施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-searching谓词

扩展RootResourceInformationHandlerMethodArgumentResolver

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

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(".*[^.]\\[.*[^\\[]");

以上是 QueryDsl Web查询关于Map字段的键 的全部内容, 来源链接: utcz.com/qa/421423.html

回到顶部