最近在寫Es的Java查詢Api,發現從部署到Api沒有很好的資料,所以整理記錄一下,配置就不發了,直接分析代碼和需求。
Es複雜查詢
基於 SpringBoot + EsTransportClient
需求分析
這個查詢的需求是,滿足中文分詞 與查詢
標題和描述(同時滿足),也可以 或查詢
多組條件拼出來的 查詢條件
(滿足一個或多個) 。比如:查詢:同時滿足黃色和好吃和產地爲南京的水果
或 同時滿足藍色和難吃和產地爲北京
這樣的組查詢。 這些查詢條件大概是這樣的 Json
:
[
{"BULLETIN_TYPE_NAME":"xxx公示",
"NOTICE_INDUSTRIES_NAME": "其他",
"REGION_PROVINCE": "江蘇xx"
},
{"BULLETIN_TYPE_NAME":"xxxx",
"NOTICE_INDUSTRIES_NAME": "市政xx",
"REGION_PROVINCE": "江蘇xx",
"REGION_NAME": "江蘇省xxx"
}
]
還要滿足默認用某個字段進行排序、支持分頁,返回所有和限制條件下的搜索數目,這裏用布爾查詢可以解決。
查詢代碼
// 查詢
@RequestMapping(value = "Query", method = RequestMethod.POST)
public ResponseEntity query(
@RequestParam(name = "NOTICE_CONTENT", required = false) String NOTICE_CONTENT,
@RequestParam(name = "NOTICE_NAME", required = false) String NOTICE_NAME,
@RequestParam(name = "Index", required = true) String index,
@RequestParam(name = "PageSize", required = false, defaultValue = "20") int PageSize,
@RequestParam(name = "CurrentPage", required = false, defaultValue = "1") int CurrentPage,
@RequestParam(name = "Type", required = true) String type,
@RequestParam(name = "BoolQueryJson", required = true) String BoolQueryJson) {
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
if (NOTICE_NAME != null) {
boolQuery.must(QueryBuilders.matchQuery("NOTICE_NAME", NOTICE_NAME));
}
if (NOTICE_CONTENT != null) {
boolQuery.must(QueryBuilders.matchQuery("NOTICE_CONTENT", NOTICE_CONTENT));
}
JSONArray jsonArray = JSONArray.fromObject(BoolQueryJson);
if (jsonArray != null && jsonArray.size() > 0) {
for (int i = 0; i < jsonArray.size(); i++) {
int j = 0;
BoolQueryBuilder boolQueryParam = QueryBuilders.boolQuery();
if (jsonArray.getJSONObject(i).has("BULLETIN_TYPE_NAME")) {
String BULLETIN_TYPE_NAMEI = jsonArray.getJSONObject(i).getString("BULLETIN_TYPE_NAME");
MatchPhraseQueryBuilder BULLETIN_TYPE_NAMEMP = QueryBuilders.matchPhraseQuery("BULLETIN_TYPE_NAME", BULLETIN_TYPE_NAMEI);
boolQueryParam.should(BULLETIN_TYPE_NAMEMP);
j++;
}
if (jsonArray.getJSONObject(i).has("NOTICE_INDUSTRIES_NAME")) {
String NOTICE_INDUSTRIES_NAMEI = jsonArray.getJSONObject(i).getString("NOTICE_INDUSTRIES_NAME");
MatchPhraseQueryBuilder NOTICE_INDUSTRIES_NAMEMP = QueryBuilders.matchPhraseQuery("NOTICE_INDUSTRIES_NAME", NOTICE_INDUSTRIES_NAMEI);
boolQueryParam.should(NOTICE_INDUSTRIES_NAMEMP);
j++;
}
if (jsonArray.getJSONObject(i).has("REGION_PROVINCE")) {
String REGION_PROVINCEI = jsonArray.getJSONObject(i).getString("REGION_PROVINCE");
MatchPhraseQueryBuilder REGION_PROVINCEMP = QueryBuilders.matchPhraseQuery("REGION_PROVINCE", REGION_PROVINCEI);
boolQueryParam.should(REGION_PROVINCEMP);
j++;
}
if (jsonArray.getJSONObject(i).has("REGION_NAME")) {
String REGION_NAMEI = jsonArray.getJSONObject(i).getString("REGION_NAME");
MatchPhraseQueryBuilder REGION_NAMEMP = QueryBuilders.matchPhraseQuery("REGION_NAME", REGION_NAMEI);
boolQueryParam.should(REGION_NAMEMP);
j++;
}
if (jsonArray.getJSONObject(i).has("TRADE_PLAT")) {
String TRADE_PLATI = jsonArray.getJSONObject(i).getString("TRADE_PLAT");
MatchPhraseQueryBuilder TRADE_PLATMP = QueryBuilders.matchPhraseQuery("TRADE_PLAT", TRADE_PLATI);
boolQueryParam.should(TRADE_PLATMP);
j++;
}
boolQueryParam.minimumShouldMatch(j);
boolQuery.should(boolQueryParam);
}
boolQuery.minimumShouldMatch(1);
}
SearchRequestBuilder builder = this.client.prepareSearch(index)
.setTypes(type)
.setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
.setFetchSource(null,new String[]{"NOTICE_CONTENT"})
.setQuery(boolQuery)
.addSort("NOTICE_SEND_TIME", SortOrder.DESC)
.setFrom((CurrentPage - 1) * PageSize)
.setSize(PageSize);
SearchResponse response = builder.get();
List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
int sum = 0;
for (SearchHit hit : response.getHits()) {
result.add(hit.getSource());
// 限制條件內的總數
sum++;
}
// 所有文檔查詢的總數
SearchHits searchHits = response.getHits();
long AllSum = searchHits.getTotalHits();
return new ResponseEntity(result, HttpStatus.OK);
}
DSL 結構
生成的 ES DSL 查詢 結構
{
"from" : 0,
"size" : 10,
"query" : {
"bool" : {
"must" : [
{
"match" : {
"NOTICE_NAME" : {
"query" : "中國石油",
"operator" : "OR",
"prefix_length" : 0,
"max_expansions" : 50,
"fuzzy_transpositions" : true,
"lenient" : false,
"zero_terms_query" : "NONE",
"boost" : 1.0
}
}
},
{
"match" : {
"NOTICE_CONTENT" : {
"query" : "中國石油",
"operator" : "OR",
"prefix_length" : 0,
"max_expansions" : 50,
"fuzzy_transpositions" : true,
"lenient" : false,
"zero_terms_query" : "NONE",
"boost" : 1.0
}
}
}
],
"should" : [
{
"bool" : {
"should" : [
{
"match_phrase" : {
"BULLETIN_TYPE_NAME" : {
"query" : "中標候選人公示",
"slop" : 0,
"boost" : 1.0
}
}
},
{
"match_phrase" : {
"NOTICE_INDUSTRIES_NAME" : {
"query" : "其他",
"slop" : 0,
"boost" : 1.0
}
}
},
{
"match_phrase" : {
"REGION_PROVINCE" : {
"query" : "江蘇",
"slop" : 0,
"boost" : 1.0
}
}
},
{
"match_phrase" : {
"REGION_NAME" : {
"query" : "江蘇省",
"slop" : 0,
"boost" : 1.0
}
}
}
],
"disable_coord" : false,
"adjust_pure_negative" : true,
"minimum_should_match" : "4",
"boost" : 1.0
}
},
{
"bool" : {
"should" : [
{
"match_phrase" : {
"BULLETIN_TYPE_NAME" : {
"query" : "招標公告",
"slop" : 0,
"boost" : 1.0
}
}
},
{
"match_phrase" : {
"NOTICE_INDUSTRIES_NAME" : {
"query" : "市政",
"slop" : 0,
"boost" : 1.0
}
}
},
{
"match_phrase" : {
"REGION_PROVINCE" : {
"query" : "江蘇",
"slop" : 0,
"boost" : 1.0
}
}
},
{
"match_phrase" : {
"REGION_NAME" : {
"query" : "江蘇省,南京市",
"slop" : 0,
"boost" : 1.0
}
}
}
],
"disable_coord" : false,
"adjust_pure_negative" : true,
"minimum_should_match" : "4",
"boost" : 1.0
}
}
],
"disable_coord" : false,
"adjust_pure_negative" : true,
"minimum_should_match" : "1",
"boost" : 1.0
}
},
"stored_fields" : "NOTICE_SEND_TIME",
"sort" : [
{
"NOTICE_SEND_TIME" : {
"order" : "desc"
}
}
]
}
Es客戶端Bean Config
@Configuration
public class MyConfig {
@Bean
public TransportClient client() throws UnknownHostException {
InetSocketTransportAddress node = new InetSocketTransportAddress(
InetAddress.getByName("192.168.1.21"),
9300
);
Settings settings = Settings.builder()
.put("cluster.name","leon")
.build();
TransportClient client = new PreBuiltTransportClient(settings);
client.addTransportAddress(node);
return client;
}
}
查詢結果
總結
- 注意引入的
jar
包版本一致性 - 多個條件一起拼湊組成了 一個
should
,嵌套構成了bool
查詢,要注意循環的順序,即:添加到外層的時機。
比如if
中的
boolQueryParam.should(REGION_NAMEMP);
boolQuery.should(boolQueryParam);
如果我合併寫成
boolQuery.should(boolQueryParam.should(REGION_NAMEMP));
構造的 builder
就會出現8個should ,顯然不是我要的結果
3. 如果熟悉 Es
,以前用過 Kirbna
查詢結果和預期不一致,可以打印 builder
通過查看 Es
生成的 Json 查詢體來查錯。
4. searchHits.getTotalHits();
並不等於 response.getHits()
的 數目
5. .setFetchSource(null,new String[]{"NOTICE_CONTENT"})
設置不反回某個字段,當然inclunde
部分可以設置返回,具體參考 SearchRequestBuilder
方法,可以指定 inclunde
和 exclude
。