背景
当我们使用了Elasticsearch,我们就需要考虑如何去访问Elasticsearch上面的数据,目前官方推荐RestHighLevelClient SDK去操作es数据,但是使用过的同学的知道,要构造这个这个请求对象很麻烦,特别是当我们的查询复杂的时候。 下面有一段本blog使用Sdk去访问es的代码,看起来就知道构造一个查询对象麻烦。
代码语言:javascript复制RestHighLevelClient client = new RestHighLevelClient(restClient);
// 创建并设置SearchSourceBuilder对象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
BoolQueryBuilder filterQueryBuilder = new BoolQueryBuilder();
if(categoryId != null && categoryId != Category.INDEX_0){//首页查询所有
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("category_id",categoryId);
filterQueryBuilder.must(termQueryBuilder);
}
filterQueryBuilder.must(QueryBuilders.termQuery("states",1));//已发布的
//filterQueryBuilder.should(QueryBuilders.matchQuery("title",searchKey));
//filterQueryBuilder.should(QueryBuilders.matchQuery("summary",searchKey));
HasChildQueryBuilder hasChildQueryBuilder = new HasChildQueryBuilder(
"t_article_contents",QueryBuilders.matchQuery("contents",searchKey).boost(1), ScoreMode.None);
//filterQueryBuilder.should(hasChildQueryBuilder);
//这里的should是放在 bool query中的原本是没有作用的只有提升相关度的作用,,,但是使用.minimumShouldMatch(1); 至少匹配一个
boolQueryBuilder.should(QueryBuilders.matchQuery("title",searchKey).boost(5)).minimumShouldMatch(1);
boolQueryBuilder.should(QueryBuilders.matchQuery("summary",searchKey).boost(3)).minimumShouldMatch(1);
boolQueryBuilder.should(hasChildQueryBuilder.boost(1)).minimumShouldMatch(1);
boolQueryBuilder.filter(filterQueryBuilder);
searchSourceBuilder.query(boolQueryBuilder);
searchSourceBuilder.from(0);
// 每页多少条数据
searchSourceBuilder.size(1000);
// 设置排序规则
searchSourceBuilder.sort(new ScoreSortBuilder().order(SortOrder.DESC)); // 按_score降序排序(默认值)
searchSourceBuilder.sort("publish_time", SortOrder.DESC);
// 设置超时时间为2s
searchSourceBuilder.timeout(new TimeValue(2000));
//高亮
HighlightBuilder highlightBuilder = new HighlightBuilder();//Creates a new HighlightBuilder
highlightBuilder.preTags("<span class="highlight-es">").postTags("</span>");
HighlightBuilder.Field highlightTitle =
new HighlightBuilder.Field("title");//Create a field highlighter for the title field
highlightTitle.highlighterType("unified"); //Set the field highlighter type
highlightBuilder.field(highlightTitle); //Add the field highlighter to the highlight builder
HighlightBuilder.Field highlightSummary = new HighlightBuilder.Field("summary");
highlightBuilder.field(highlightSummary);
searchSourceBuilder.highlighter(highlightBuilder);
// 创建并设置SearchRequest对象
SearchRequest searchRequest = new SearchRequest();
// 设置request要搜索的索引和类型
searchRequest.indices("blog").types("t_article");
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest);
System.out.printf(searchResponse.toString());
SearchHits hits = searchResponse.getHits();
List<Article> publishList = new ArrayList<>();
for(SearchHit hit : hits){
Article article = new Article();
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
article.setId(Long.parseLong(hit.getId()));
article.setTitle((String)sourceAsMap.get("title"));
article.setThumbUrl((String)sourceAsMap.get("thumb_url"));
article.setSummary((String)sourceAsMap.get("summary"));
article.setClickNum(Integer.valueOf(sourceAsMap.get("click_num") ""));
article.setPublishTime((String)sourceAsMap.get("publish_time"));
article.setCreator((String)sourceAsMap.get("creator"));
Map<String, HighlightField> highMap = hit.getHighlightFields();
if(highMap!=null && !highMap.isEmpty()){
if(highMap.get("title")!=null){
article.setTitle(highMap.get("title").getFragments()[0].string());
}
if(highMap.get("summary")!=null){
article.setSummary(highMap.get("summary").getFragments()[0].string());
}
}
publishList.add(article);
}
mv.addObject("publishList",publishList);
List<Article> mostClickList = this.findMostClickArticle();
mv.addObject("mostClickList",mostClickList);
client.close();
使用过mybatis和hibernate的同学都知道,mybatis简单,易学,那么我们也可以使用elasticsearch的语法DSL来查询。
动态DSL
我们可以建一个文件我们的dsl放在resource下面,customer_dsl.xml
代码语言:javascript复制<?xml version="1.0" encoding="UTF-8"?>
<dsls>
<!--统计客户的的人数-->
<dsl id="customer.calcCustomerCount">
{
"size": 0,
"query": {
"bool": {
"must": [
{
"term": {
"type": "customer"
}
},
{
"term": {
"group_id": {
"value": "${#groupId}"
}
}
},
。。。。
。。。。。
。。。。。
。。。。。
<if test="#brandId == null and #isCustomer != null and #isCustomer">
,{
"range": {
"first_pay_time": {
"gte": "${#startTimeUtc}",
"lte": "${#endTimeUtc}"
}
}
}
</if>
]
}
}
}
</dsl>
</dsls>
解析动态的dsl语句,这里我为了简单将customer_dsl,放在c盘,这个可以转成dsl,然后使用RestHighLevelClient来直接执行DSL.
代码语言:javascript复制public static void main(String[] args) throws Exception {
DefaultQueryDSLFactory dslFactory = new DefaultQueryDSLFactory("file:c:/customer_dsl.xml");
Map<String, Object> searchMap = new HashMap<>();
searchMap.put("groupId",123);
String s = dslFactory.getQueryDsl("customer.calcCustomerCount", searchMap);
System.out.println(s);
}
上面看到这个语法${#groupId},可能会觉得很奇怪,不像mybatis啊,这个是使用SpEl语法#groupId,然后我们又重写了前缀,和后缀的方法
代码语言:javascript复制private final String expressionPrefix = "${";
private final String expressionSuffix = "}";
@Override
public final String getExpressionPrefix() {
return this.expressionPrefix;
}
@Override
public final String getExpressionSuffix() {
return this.expressionSuffix;
}
可以配置项目启动的创建bean会自动加载dsl.xml文件放在缓存中
代码语言:javascript复制@Configuration
public class DefaultQueryDSLFactoryConfig {
@Bean
public DefaultQueryDSLFactory createDefaultQueryDSLFactory() throws Exception {
DefaultQueryDSLFactory defaultQueryDSLFactory = new DefaultQueryDSLFactory("classpath*:**/*_dsl.xml");
return defaultQueryDSLFactory;
}
}
代码