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字段。
我尝试通过拥有存储库来定制绑定
extend QuerydslBinderCustomizer<QMeetup>
并覆盖
customize(QuerydslBindings bindings, QMeetup meetup)
方法,但是当customize()方法被点击时,lambda内部的绑定代码却没有。
customize()
编辑:了解到这是因为QuerydslBindings评估查询参数的方法不允许它与pathSpecs它内部持有的地图相匹配-里面有您的自定义绑定。
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; }); }
QuerydslPredicateBuilder.getPredicate()
QuerydslBindings.getPropertyPath()
QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver.postProcess()
如何覆盖该类或替换Bean?它被实例化,并在RepositoryRestMvcConfiguration.repoRequestArgumentResolver()bean声明中作为bean返回。
RepositoryRestMvcConfiguration.repoRequestArgumentResolver()
repoRequestArgumentResolver
RepositoryRestMvcConfiguration
@Primary``@Ordered(HIGHEST_PRECEDENCE)
RepositoryRestMvcConfiguration.class
RepositoryRestMvcConfiguration's
好吧-好像我所期望的支持不存在。
所以问题就变成了: 我该 如何 正确地覆盖repoRequestArgumentResolverbean?
BTW- QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver尴尬的非公开。:/
QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver
这就是我在应用程序上下文中替换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-search谓词的代码段。再次-很想知道更好的方法。
该postProcess方法调用:
postProcess
predicate = addCustomMapPredicates(parameterMap, predicate, domainType).getValue();
就 在将predicate引用传递到QuerydslRepositoryInvokerAdapter构造函数并返回之前。
predicate
QuerydslRepositoryInvokerAdapter
这是该addCustomMapPredicates方法:
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(".*[^.]\\[.*[^\\[]");