最近做的產(chǎn)品基于spring boot, mongo進(jìn)行開發(fā)紊服,由于前端需要進(jìn)行比較復(fù)雜的查詢,因此引入了dsl相關(guān)包乳愉,版本信息如下:
com.querydsl:querydsl-mongodb:jar:4.1.4,
org.springframework.boot:spring-boot-devtools:jar:1.4.1.RELEASE,
org.springframework:spring-context-support:jar:4.3.3.RELEASE
并定義了dsl相關(guān)的接口送挑,如下所示:
public interface VipRepository extends CrudRepository<Vip, String>, QueryDslPredicateExecutor<Vip>, QuerydslBinderCustomizer<Vip>
, MongoRepository<Vip, String>{
Override
default public void customize(QuerydslBindings bindings, Vip root) {
log.debug ("[VipRepository]from customize");
bindings.bind (String.class)
.first ((StringPath path, String value) -> path.containsIgnoreCase (value));
}
}
Resource中注入該dao:
@RestController
@RequestMapping("/v1/vip")
public class VipResource {
private final Logger log = LoggerFactory.getLogger (VipResource.class);
@Inject
private VipRepository vipRepository;
@RequestMapping(value = "/vip/list", method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
@Timed
@Secured(AuthoritiesConstants.ADMIN)
public ResponseEntity<List<Vip>> listVip(@QuerydslPredicate(root =
Vip.class) Predicate predicate,
Pageable pageable) throws
URISyntaxException {
log.debug ("[viplist]");
Page<Vip> page = vipRepository.findAll (predicate, pageable);
log.debug ("[listSimMonthGprs] page total elements: {}", page.getTotalElements ());
HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders (page,
"/v1/vip/list");
return new ResponseEntity<> (results, headers, HttpStatus.OK);
}
...
但在測試中發(fā)現(xiàn),在調(diào)用/vip/list時(shí)哈误,
有時(shí)會(huì)調(diào)用VipRepository中的customize方法哩至,有時(shí)卻不會(huì)。
實(shí)在奇怪黑滴。嘗試在VipRepository添加日志憨募,或者加斷點(diǎn)調(diào)試,添加serializable接口都是一樣的效果袁辈,只有spring context啟動(dòng)后第一次調(diào)用該接口加載不上菜谣,以后也都加載不上。
于是嘗試從spring data,querydsl的源碼進(jìn)行調(diào)試尾膊。在未深入研究spring data和querydsl源碼的情況下如何加斷點(diǎn)呢媳危?觀察到在customize方法的參數(shù)中引入有QuerydslBindings這個(gè)綁定接口,結(jié)合之前對(duì)querydsl的研究冈敛,該接口完成domain類和Q類之前的參數(shù)綁定待笑,于是利用IDE的功能找到該接口的實(shí)現(xiàn)類QuerydslBindingsFactory,該類源碼如下:
public class QuerydslBindingsFactory implements ApplicationContextAware {
private final EntityPathResolver entityPathResolver;
private final Map<TypeInformation<?>, EntityPath<?>> entityPaths;
private AutowireCapableBeanFactory beanFactory;
//該類cache了系統(tǒng)中所有的domain類與repository名稱的鍵值對(duì)map
private Repositories repositories;
public QuerydslBindings createBindingsFor(Class<? extends QuerydslBinderCustomizer<?>> customizer,
TypeInformation<?> domainType) {
Assert.notNull(domainType, "Domain type must not be null!");
EntityPath<?> path = verifyEntityPathPresent(domainType);
QuerydslBindings bindings = new QuerydslBindings();
findCustomizerForDomainType(customizer, domainType.getType()).customize(bindings, path);
return bindings;
}
/**
* Tries to detect a Querydsl query type for the given domain type candidate via the configured
* {@link EntityPathResolver}.
*
* @param candidate must not be {@literal null}.
* @throws IllegalStateException to indicate the query type can't be found and manual configuration is necessary.
*/
private EntityPath<?> verifyEntityPathPresent(TypeInformation<?> candidate) {
EntityPath<?> path = entityPaths.get(candidate);
if (path != null) {
return path;
}
Class<?> type = candidate.getType();
try {
path = entityPathResolver.createPath(type);
} catch (IllegalArgumentException o_O) {
throw new IllegalStateException(
String.format(INVALID_DOMAIN_TYPE, candidate.getType(), QuerydslPredicate.class.getSimpleName()), o_O);
}
entityPaths.put(candidate, path);
return path;
}
/**
* Obtains the {@link QuerydslBinderCustomizer} for the given domain type. Will inspect the given annotation for a
* dedicatedly configured one or consider the domain types's repository.
*
* @param annotation
* @param domainType
* @return
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private QuerydslBinderCustomizer<EntityPath<?>> findCustomizerForDomainType(
Class<? extends QuerydslBinderCustomizer> customizer, Class<?> domainType) {
if (customizer != null && !QuerydslBinderCustomizer.class.equals(customizer)) {
return createQuerydslBinderCustomizer(customizer);
}
if (repositories != null && repositories.hasRepositoryFor(domainType)) {
Object repository = repositories.getRepositoryFor(domainType);
if (repository instanceof QuerydslBinderCustomizer) {
return (QuerydslBinderCustomizer<EntityPath<?>>) repository;
}
}
return NoOpCustomizer.INSTANCE;
}
/**
* Obtains a {@link QuerydslBinderCustomizer} for the given type. Will try to obtain a bean from the
* {@link org.springframework.beans.factory.BeanFactory} first or fall back to create a fresh instance through the
* {@link org.springframework.beans.factory.BeanFactory} or finally falling back to a plain instantiation if no
* {@link org.springframework.beans.factory.BeanFactory} is present.
*
* @param type must not be {@literal null}.
* @return
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private QuerydslBinderCustomizer<EntityPath<?>> createQuerydslBinderCustomizer(
Class<? extends QuerydslBinderCustomizer> type) {
if (beanFactory == null) {
return BeanUtils.instantiateClass(type);
}
try {
return beanFactory.getBean(type);
} catch (NoSuchBeanDefinitionException e) {
return beanFactory.createBean(type);
}
}
/**
*
**/
private static enum NoOpCustomizer implements QuerydslBinderCustomizer<EntityPath<?>> {
INSTANCE;
@Override
public void customize(QuerydslBindings bindings, EntityPath<?> root) {}
}
}
This class will be invoked before entering resource method. It is charge of generating predicate instance from request parameters.
At first, calling createBindingsFor method. In the method, invoking verifyEntityPathPresent to get path. Then call findCustomizerForDomainType to check if dsl repository exists.
Pay attention to this sentence:
Object repository = repositories.getRepositoryFor(domainType);
This sentence gets repository object according to domainType. When using Vip to query, I found it return another repository for Vip not DSL repository.
到這個(gè)地方抓谴,大家就比較清楚原因了:因?yàn)橄到y(tǒng)中針對(duì)同一個(gè)domain定了兩個(gè)repository暮蹂,而spring在加載時(shí)使用domain class作為key,repository name作為值癌压,只能隨機(jī)cache一個(gè)repository仰泻,這也是有時(shí)可以調(diào)用到customize,而有時(shí)不可以的原因滩届。
找到原因后集侯,該問題就很好解決了,把兩個(gè)repository接口融合到一起即可帜消。