QueryDsl Web查询Map字段的键
概观
特定
- Spring Data JPA,Spring Data Rest,QueryDsl
- 一个
Meetup
实体- 使用
Map properties
字段- 作为
@ElementCollection
持久存储在MEETUP_PROPERTY
表中
- 作为
- 使用
- 一个
MeetupRepository
- 扩展了
QueryDslPredicateExecutor
- 扩展了
我期待
一个Web查询
GET /api/meetup?properties[aKey]=aValue
仅返回具有指定键和值的属性条目的Meetup:aKey = aValue。
但是,这对我不起作用。 我错过了什么?
试着
简单的领域
简单的字段起作用,如名称和描述:
GET /api/meetup?name=whatever
收集领域像参与者一样工作:
GET /api/meetup?participants.name=whatever
但不是这个Map字段。
自定义QueryDsl绑定
我已经尝试通过拥有存储库来自定义绑定
extend QuerydslBinderCustomizer
并凌驾于
customize(QuerydslBindings bindings, QMeetup meetup)
方法,但正在命中customize()
方法时,lambda中的绑定代码不是。
编辑:了解这是因为QuerydslBindings
评估查询参数的方法不要让它与它内部持有的pathSpecs
映射匹配 – 它有自定义绑定。
一些细节
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 properties = new HashMap();
定制的querydsl绑定
编辑:见上文; 事实certificate,这对我的代码没有任何作用。
public interface MeetupRepository extends PagingAndSortingRepository, QueryDslPredicateExecutor, QuerydslBinderCustomizer { @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; }); }
其他调查结果
-
QuerydslPredicateBuilder.getPredicate()
要求QuerydslBindings.getPropertyPath()
尝试2种方法来返回路径,这样它就可以生成QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver.postProcess()
可以使用的谓词。- 1是查看自定义绑定。 我没有看到任何方式在那里表达地图查询
- 2是默认为Spring的bean路径。 那里的表达问题相同。 你怎么表达地图? 所以看起来不可能让
QuerydslPredicateBuilder.getPredicate()
自动创建一个谓词。 好的 – 我可以手动完成,如果我可以挂钩到QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver.postProcess()
我该如何覆盖该类,或者替换bean? 它在RepositoryRestMvcConfiguration.repoRequestArgumentResolver()
bean声明中实例化并作为bean返回。
- 我可以通过声明我自己的
repoRequestArgumentResolver
bean 来覆盖该bean,但它不会被使用。- 它被
RepositoryRestMvcConfiguration
覆盖。 我不能通过设置@Primary
或@Ordered(HIGHEST_PRECEDENCE)
强制它。 - 我可以通过显式组件扫描
RepositoryRestMvcConfiguration.class
强制它,但这也会混淆Spring Boot的自动配置,因为它会导致在任何自动配置运行之前处理RepositoryRestMvcConfiguration's
bean声明。 除其他外,这导致jackson以不必要的方式序列化的回应。
- 它被
问题
嗯 – 看起来像我预期的支持不存在。
所以问题就变成了:我如何正确覆盖repoRequestArgumentResolver
bean?
BTW – QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver
非常不公开。 :/
替换Bean
实现ApplicationContextAware
这就是我在应用程序上下文中替换bean的方式。
感觉有点hacky。 我很想听到一个更好的方法来做到这一点。
@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'.
* 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.
* 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 params创建一个Map-searching谓词
扩展RootResourceInformationHandlerMethodArgumentResolver
这些是基于http查询参数创建自己的地图搜索谓词的代码片段。 再次 – 希望知道更好的方式。
postProcess
方法调用:
predicate = addCustomMapPredicates(parameterMap, predicate, domainType).getValue();
就在predicate
引用传递给QuerydslRepositoryInvokerAdapter
构造函数并返回之前。
这是addCustomMapPredicates
方法:
private BooleanBuilder addCustomMapPredicates(MultiValueMap 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(".*[^.]\\[.*[^\\[]");
- 如何将水平/垂直滚动条添加到JScrollPane
- javax.persistence.NoResultException:getSingleResult()没有检索任何实体
- 如何在JSP中将值插入数据库到derby?
- 创建一个方法,接受可变长度的Function参数,可能有不同的类型
- Spring 3安全性:未调用AccessDeniedHandler
- RDMA(JSOR)上的Java套接字与Infiniband中的jVerbs性能
- Java客户端服务器聊天程序
- 如何正确地为数据结构中的图像设置动画而不是获取ConcurrentModificationException
- Java TripleDESCryptoServiceProvider等效于Java