栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 前沿技术 > 大数据 > 大数据系统

spring-data-elasticsearch中常用的查询api使用和对比

spring-data-elasticsearch中常用的查询api使用和对比

负一、 repository

可以通过继承Repository的方式去快速的实现查询操作
官方文档里面有写

https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#elasticsearch.repositories

通过表格中字段的拼接去定义一个方法,就可以进行查询,实在是很方便,所以我这里就没写关于Repository的内容。

零 、ES搬砖总共分几步

模板类我使用的是:

	@Autowired
	ElasticsearchRestTemplate template;

核心使用的类有

Query

NativeQuery

QueryBuilder

QueryBuilders

大部分的操作基本就是 上边四个类,使用方法 分为三步

template.search(query)query=new NativeQuery(queryBuilder)queryBulder=QueryBuilder.xxxxBuilder();

会了这三步,基本就都会了,全剧终,再见~











正文

我们假设我们es中有一些手机的商品数据,我现在想要进行一些查询,然后看看查询结果,对比一下api的使用的区别。

java侧有一个产品类EsProduct,我们要使用的字段是name、和subTitle,可以看到都是text类型,且经过ik分词了
es侧有个手机名称叫 【华为 HUAWEI P20】

@Data
@EqualsAndHashCode(callSuper = false)
@document(indexName = "pms",type="product",shards = 1,replicas = 0)
public class EsProduct implements Serializable {
    private static final long serialVersionUID = -1L;
    @Id

    private Long id;
    @Field(type = FieldType.Keyword)
    private String productSn;
    private Long brandId;
    @Field(type = FieldType.Keyword)
    private String brandName;//品牌
    private Long productCategoryId;
    @Field(type = FieldType.Keyword)
    private String productCategoryName;//产品类别名称
    private String pic;
    @Field(analyzer = "ik_max_word",type = FieldType.Text)
    private String name;
    @Field(analyzer = "ik_max_word",type = FieldType.Text)
    private String subTitle;
    @Field(analyzer = "ik_max_word",type = FieldType.Text)
    private String keywords;
    private BigDecimal price;
    private Integer sale;
    private Integer newStatus;
    private Integer recommandStatus;
    private Integer stock;
    private Integer promotionType;
    private Integer sort;
    @Field(type =FieldType.Nested)
    private List attrValueList;
}
一、match 与term 1、举个栗子一

首先写了一个get方法,传入相关的手机名称

   @GetMapping("/search1/{name}")
    public void search1(@PathVariable("name") String name) {
        Query query = null;
        QueryBuilder queryBuilder = null;

        queryBuilder = QueryBuilders.matchQuery("name", name);

        query = new NativeSearchQuery(queryBuilder);
        SearchHits search = template.search(query, EsProduct.class);
    }

结果:

1.当输入华为、或华为哥哥时,就可以搜索到华为手机2.如果输入华,就搜索不到,因为es中 ‘华为’ 分词后 并没有分解成‘华’ 和‘为’,却仍然是 ‘华为’,所以搜索不到
结论1.matchQuery 是指 对name进行分词,成为数组 arr1[a,b…],然后分别将a,b…与es数据中的name属性的索引去对比,只要有一个对比上了,就算符合。2.举例:如果name属性不是keyword类型时,会被分词为【华为, HUAWEI, P20,P,20】,当输入华为哥哥时,会分词为【华为,哥哥】,可以看到两个数组 的 【华为】匹配到了,所以最终可以查到 华为手机。3.如果输入【华】、【为华】,就不能查到。原因如上另外说一下,可在kibana中,自己分词查一下:例如:
2、举个栗子二

  @GetMapping("/search2/{name}")
    @ApiOperation("通过name查询名字中带有name的数据 term query(短语查询)")
    public void search2(@PathVariable("name") String name) {
        Query query = null;
        QueryBuilder queryBuilder = null;
        queryBuilder = QueryBuilders.termQuery("name", name);
        query = new NativeSearchQuery(queryBuilder);
        SearchHits search = template.search(query, EsProduct.class);
    }

结果:

输入 华为,可以搜索到输入 华 ,失败输入 华为哥哥,搜索不到

结论

1.term 查询与上述match查询的区别在于,match查询时会将 输入的name进行分词,只有分词数组中有一个匹配到了es中的索引,就算搜索成功。term 与之相反,不对 输入的name进行分词 二、match query与fuzzy query 1.举个栗子一

结合结论看栗子

 @GetMapping("/search3/{name}")
    @ApiOperation("通过name查询名字中带有name的数据 match query 与fuzzy query(模糊查询)")
    public void search3(@PathVariable("name") String name) {
        Query query = null;
        QueryBuilder queryBuilder = null;
        queryBuilder = QueryBuilders.fuzzyQuery("name", name).fuzziness(Fuzziness.ONE);
        query = new NativeSearchQuery(queryBuilder);
    }

总结

1.match query时携带模糊查询,首先 输入的name 会被分词,.fuzziness参数会指定编辑次数,fuzziness.zone 为绝对匹配,fuzziness.one为编辑一次

fuzziness.two为编辑两次。fuzziness.auto 为默认推荐的方式

这篇文章讲的有挺好(https://www.jianshu.com/p/06f43b537a29)

编辑一次指:

1.将一个字符替换成另一个字符

2.插入一个字符

3.删除一个字符

4.两个字符进行位置交换(莱文斯坦距离【Levenshtein distance】算法中 视之为两次编辑,而【Damerau–Levenshtein distance】视为一次编辑【默认是这种算法】)


一.结合上方栗子,在fuzzyQuery(“name”,name)后添加fuzziness参数 如下:

queryBuilder= QueryBuilders.matchQuery("name",name).fuzziness(Fuzziness.ONE);

1、输入华为哥,分词后变成【华为、哥】,但是因为是match查询,所有不需要模糊也能搜索到华为

但是上述分词中还有【哥】,所以可视之 为删除掉,变成了 空字符串,所以除了华为之外,还可以匹配到其他的数据 2、输入华为哥哥,分词后变成【华为、哥哥】,华为手机依然能搜索到。

但是【哥哥】这个分词无论加一个字符、删字符、变化位置,都不能与其他索引匹配,所以最终只能查到华为手机


二、结合上方栗子,继续添加.prefixLength(1)参数,表示在term级别上至少要匹配1个字符后才能进行上述的编辑,如下:

queryBuilder= QueryBuilders.matchQuery("name",name).fuzziness(Fuzziness.ONE).prefixLength(1);
1.输入华为哥,分词后为【华为、哥】,【华为】可以匹配到华为手机

但是这回哥字就不能删除了,必要要和其他索引匹配1个字符后才能开始编辑,所以最终只能匹配到华为手机


三、结合上方栗子,添加transpostion(true|false),true(默认)时为Damerau–Levenshtein distance,false为莱文斯坦-距离算法

	 queryBuilder= QueryBuilders.matchQuery("name",name).fuzziness(Fuzziness.ONE).fuzzyTranspositions(true);

1.输入: 为华,分词为【‘为’,‘华‘】,这样编辑一次,就可以删除掉,所有匹配的是所有的数据2.使用fuzzyQuery进行查询,如下:

queryBuilder=QueryBuilders.fuzzyQuery("name",name).fuzziness(Fuzziness.ONE);;

输入: 为华,因为fuzzyQuery是 不会对需要检索的内容进行分词,所以term级别的短语是【为华】,所以编辑一次时,会将 这两个字进行调换,最终查到的是华为手机


三、query 和filter

query会计算相关度从而得到score,filter是从query的结果中进行筛选,不进行score的计算

     
    @GetMapping("/search4")
    @ApiOperation("在商品名称和描述中查询,品牌名称和种类进行筛选【根据商品价格进行排序】")
    public voidsearch4(@RequestParam(required = false) String brandName,
                          @RequestParam(required = false) String productCategoryName,
                          @RequestParam(required = false) String keyword) {

        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();


        //keyword 匹配 name 或 subTitle
        if (StringUtils.isEmpty(keyword)) {
            //如果没有输入关键字,就匹配所有 
            nativeSearchQueryBuilder.withQuery(QueryBuilders.matchAllQuery());
        } else {
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            boolQueryBuilder.should().add(QueryBuilders.matchQuery("name", keyword));
            boolQueryBuilder.should().add(QueryBuilders.matchQuery("subTitle", keyword));
            nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
        }

        //过滤 品牌和种类
        BoolQueryBuilder filter = QueryBuilders.boolQuery();
        if (!StringUtils.isEmpty(brandName)) {
            filter.must().add(QueryBuilders.termQuery("brandName", brandName));
        }
        if (!StringUtils.isEmpty(productCategoryName)) {
            filter.must().add(QueryBuilders.termQuery("productCategoryName", productCategoryName));
        }

        //page 分页
        nativeSearchQueryBuilder.withPageable(PageRequest.of(1,2));
        //sort 根据价格进行排序
        nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("price"));

        String result = "";
        nativeSearchQueryBuilder.withFilter(filter);
        NativeSearchQuery query = nativeSearchQueryBuilder.build();
    }

说一下我对es的dsl的学习方法,一方面看官方文档外,其实我最喜欢的方式是通过spring-data-elasticsearch提供的template去一一尝试各个方法,debug代码,把DSL复制出来,然后放到kibana中运行,就可以自己创造样例去学习了,如下图这般

四、boost

QueryBuilder对象后接.boost(float),会将该query的score * boost所设置的值

 @GetMapping("/search5")
    @ApiOperation("name查询,name 优先显示,分数不够,乘法来凑")
    public void search5(@RequestParam(required = false) String keyword) {

        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();

        //keyword 匹配 name 或 subTitle
        if (StringUtils.isEmpty(keyword)) {
            nativeSearchQueryBuilder.withQuery(QueryBuilders.matchAllQuery());
        } else {
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

            boolQueryBuilder.should().add(QueryBuilders.matchQuery("name", keyword).boost(2));
            boolQueryBuilder.should().add(QueryBuilders.matchQuery("subTitle", keyword));
            nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
        }
        NativeSearchQuery query = nativeSearchQueryBuilder.build();
        SearchHits search = template.search(query, EsProduct.class);
        

结论

该方法的目的是在 手机的名称 或 描述中查找 【华为】(注意是或),如果手机的名称中就带有华为,那就把分数×2。目的是优先显示名字有关键字的数据。


五、function_score

我们创建一个查询,一般都是通过QueryBuilders,现在要说的是 其下的functionScoreQuery方法,这个方法就是用于处理和自定义分数有关的操作


首先看一下他的五种重构,实际上 就是两种
其一:ScoreFunctionBuilder function

(不妨直接理解字面意思:分数方法 的构建器)


该对象 通过 建造者 ScoreFunctionBuilders来创建 一个 分数方法的构建器


都可以创建什么种类的呢?

1.可以让他们按照我给的权重来判分 weightFactorFunction( 权重乘法 ) ,例:ScoreFunctionBuilders.weightFactorFunction(2); 将权重变成 2

2.可以让他们先按默认的算分,然后再乘上 我给的某个数值类型的字段fieldValueFactorFunction

例如:按销售量算分。问:那如果销售量太大算出来的分有点太大怎么办?答:可以乘以一个倍数啊,比如 *0.001 ,代码上的实现就是 .factor(float)问:乘以倍数 也是线性的放大或缩小,我想不要线性的,我想要对数类型的曲线变化怎么办?答:也可以作答啊,代码上实现就是.modifier(Modifier.log)问:本来我的分数是0,log O=NAN啊,因为10的n次方中,n等于多少时 都不可能计算得到0的,那怎么办答:可以使用modifiler.log1p 或者log2p啊问:为什么刚才乘上字段的值,而不是加上字段的值,或者取平均数啊,可以做到么?答:默认是做乘法,但也可以你想要的计算操作。不过不是在 FunctionScoreQueryBuilder(其父类也不能设置)中设置,而是再querybuilder中设置例子:

FieldValueFactorFunctionBuilder price = ScoreFunctionBuilders.fieldValueFactorFunction("price").modifier(FieldValueFactorFunction.Modifier.LOG);
FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(price);
functionScoreQueryBuilder.boostMode(CombineFunction.SUM) ---看这句 ,不就实现了么

3.可以让他们按0-1随机给分吧 ,那就选用 randomFunction

4.还有其他方法可以自己看看吧,有一些与地址位置相关的。


其二: filterFunctionBuilder

A:(仔细看) 我们不忘初心,回到起点,最初我们只是 想要调动 QueryBuilders.functionScoreQuery() 进行一个查询啊,

B: 是啊,然后我们传入了 一个scoreFunctionBuilder啊,有什么问题么?

A: 你刚才设置了几次权重?

B: 设置了一次,设置为2 啊!

A: 嗯,你说的没问题,那我再问你,你刚才操作了几个字段?

B: 操作了一个字段

A: 那我现在想操作多个字段,设置不同的权重怎么办

B: 呃…这有什么意义

A: 比如我想做一个查询,我是大众点评软件,想查询 ‘水煮鱼’后,显示 销量高,评分高,然后是有wifi

显然销量和评分都很重要,但是我们软件呢 是良心软件,更注重评分,评分高的自然要排在推荐的最上边不过也有可能有人恶意的去给低分,那么如果销量很高时,我们也可以不太注重评分,从而将他排在前边。最后是wifi了,有的话,当然好了,没有的话,也不太重要。好了,以上情景给出来了。显然评分最重要,销量次之,最后是wifi,我们可以先看看 这几个参数的数值对比,假设有两家店都直接叫‘水煮鱼’,和我的搜索完美契合,就假设这样算出的分是1分的话,评分最多是5分,销量可能是成百上千,wifi是有或无,那么从最简单的来,我们就设置wifi的权重是1分吧。

	FunctionScoreQueryBuilder.FilterFunctionBuilder wifi=
     new FunctionScoreQueryBuilder.FilterFunctionBuilder
      (
       QueryBuilders.termQuery("wifi","有"),ScoreFunctionBuilders.weightFactorFunction(1);//查询wifi 为有,设置权重为1
     );

评分呢…就算五分吧

FunctionScoreQueryBuilder.FilterFunctionBuilder grade=
 new FunctionScoreQueryBuilder.FilterFunctionBuilder
 (
	 ScoreFunctionBuilders.fieldValueFactorFunction("grade")
 );

B:等等!我记得这默认是乘法吧,听你刚才的意思,好像我们要用加法的!~~

A:是的!,别急嘛!follow me~~~
* 最后是销量了…,我打开了大众点评一看,销售都是按月售的,基本最多的是几千条吧,那我们就用log1p吧,如果10的3次是1千,4次方 是1万,
* 也就是说如果销量了 1千-1万之间的话,就能得3分,好像并不能有效区分啊,那我们还是除以1000吧,这样就相当于 1000的销量可以媲美 评分1分。

FunctionScoreQueryBuilder.FilterFunctionBuilder sales=new FunctionScoreQueryBuilder.FilterFunctionBuilder(
	 ScoreFunctionBuilders.fieldValueFactorFunction("sales").factor(0.001f)
)

A: 好,我们最后都弄完了,最终得到了三个对象 wifi,grade,sales 都是 FunctionScoreQueryBuilder.FilterFunctionBuilder 类型的
* 接下来放到一个数组中
* FunctionScoreQueryBuilder.FilterFunctionBuilder[] arr=new FunctionScoreQueryBuilder.FilterFunctionBuilder[3];
* 不忘初心!我们写出我们最开始要干的事:最一个自定义的分数查询:
* QueryBuilders.functionScoreQuery()
* 将arr 作为参数参入进去 QueryBuilders.functionScoreQuery(arr);

B:刚才说好的 三个得分要相加呢

A: 哦,差点忘了

queryBuilder= QueryBuilders.functionScoreQuery(arr).boostMode(CombineFunction.SUM)
简单练习
/**
     *  接下来简单练习一下:
     * 还有一种用法是: 我现在是大学招生办的人,现在开始筛选学生的志愿。
     * 学生呢,有编号为1,2,3 的三个志愿可填写。可重复填写(三个志愿都写的我们学校),我们学校呢 想招生100个人。
     * 对于第一志愿 就选我们学校的人,我们特别重视,第二志愿次之,第三志愿再次之,如果三个志愿都填写的我们学校,那这样的人我们最喜欢
     * 除此之外呢,我们要学生的分数要超过670分才能被招生进来
     *
     * 好,情景为以上信息。
     *   1.先找到 分数大于670分的人
     *   2.我们分别在三个志愿中查询,三个志愿的权重分别是 10,5,2 ,且最少有一个志愿填写了我们学校。
     *   3.只要前100名
 //分数要大于670
    RangeQueryBuilder score = QueryBuilders.rangeQuery("score").gt(670);

    FunctionScoreQueryBuilder.FilterFunctionBuilder[] arr = new FunctionScoreQueryBuilder.FilterFunctionBuilder[3];
    String schoolName = "哈哈大学";

    FunctionScoreQueryBuilder.FilterFunctionBuilder voluntary1 = new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.termQuery("voluntary1", schoolName), ScoreFunctionBuilders.weightFactorFunction(10));
    FunctionScoreQueryBuilder.FilterFunctionBuilder voluntary2 = new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.termQuery("voluntary2", schoolName), ScoreFunctionBuilders.weightFactorFunction(10));
    FunctionScoreQueryBuilder.FilterFunctionBuilder voluntary3 = new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.termQuery("voluntary3", schoolName), ScoreFunctionBuilders.weightFactorFunction(10));
    arr[0] = voluntary1;
    arr[1] = voluntary2;
    arr[2] = voluntary3;
    FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(score, arr);
    Query query = new NativeSearchQueryBuilder().withQuery(functionScoreQueryBuilder).withPageable(PageRequest.of(0,100)).build();
    template.search(query, Student.class);
回到 重载方法

上方两种方式 都额外还有一种重载方法,就是增加了一个queryBuilder的参数。那有什么区别呢。


你可以创建一个普通的queyBulder测试一下

   @GetMapping("/search9")
    public void search9(String name) {
        FunctionScoreQueryBuilder.FilterFunctionBuilder[] arr = new FunctionScoreQueryBuilder.FilterFunctionBuilder[2];

        FunctionScoreQueryBuilder.FilterFunctionBuilder item1 =
                new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("name", name),
                        ScoreFunctionBuilders.weightFactorFunction(10));
        FunctionScoreQueryBuilder.FilterFunctionBuilder item2 =
                new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("subTitle", name),
                        ScoreFunctionBuilders.weightFactorFunction(5));
        arr[0] = item1;
        arr[1] = item2;

        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        boolQueryBuilder.should().add(QueryBuilders.matchQuery("name", name));
        boolQueryBuilder.should().add(QueryBuilders.matchQuery("subTitle", name));
        //有查询,在查询结果后 对分数进行filter
        FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(boolQueryBuilder, arr).scoreMode(FunctionScoreQuery.ScoreMode.SUM);

        Query query = new NativeSearchQueryBuilder().withQuery(functionScoreQueryBuilder).build();
        SearchHits search = template.search(query, EsProduct.class);
        print(search);
    }

然后和上述一样传入方法中,然后debug看一下 DSL
可以发现,加入query查询的DSL 会先match查询,然后再用function_score去计算分数,而反之不加入query查询的,在query阶段会直接matchALL

加了query查询

{
  "function_score" : {
    "query" : {
      "bool" : {
        "should" : [ 加了query 的会进行查询
          {
            "match" : {
              "name" : {
                "query" : "手机",
                "operator" : "OR",
                "prefix_length" : 0,
                "max_expansions" : 50,
                "fuzzy_transpositions" : true,
                "lenient" : false,
                "zero_terms_query" : "NONE",
                "auto_generate_synonyms_phrase_query" : true,
                "boost" : 1.0
              }
            }
          },
          {
            "match" : {
              "subTitle" : {
                "query" : "手机",
                "operator" : "OR",
                "prefix_length" : 0,
                "max_expansions" : 50,
                "fuzzy_transpositions" : true,
                "lenient" : false,
                "zero_terms_query" : "NONE",
                "auto_generate_synonyms_phrase_query" : true,
                "boost" : 1.0
              }
            }
          }
        ],
        "adjust_pure_negative" : true,
        "boost" : 1.0
      }
    },
    "functions" : [
      {
        "filter" : {
          "match" : {
            "name" : {
              "query" : "手机",
              "operator" : "OR",
              "prefix_length" : 0,
              "max_expansions" : 50,
              "fuzzy_transpositions" : true,
              "lenient" : false,
              "zero_terms_query" : "NONE",
              "auto_generate_synonyms_phrase_query" : true,
              "boost" : 1.0
            }
          }
        },
        "weight" : 10.0
      },
      {
        "filter" : {
          "match" : {
            "subTitle" : {
              "query" : "手机",
              "operator" : "OR",
              "prefix_length" : 0,
              "max_expansions" : 50,
              "fuzzy_transpositions" : true,
              "lenient" : false,
              "zero_terms_query" : "NONE",
              "auto_generate_synonyms_phrase_query" : true,
              "boost" : 1.0
            }
          }
        },
        "weight" : 5.0
      }
    ],
    "score_mode" : "sum",
    "max_boost" : 3.4028235E38,
    "boost" : 1.0
  }
}

不加query查询

{
  "function_score" : {
    "query" : {
      "match_all" : { 不加query的会直接match_all
        "boost" : 1.0
      }
    },
    "functions" : [
      {
        "filter" : {
          "match" : {
            "name" : {
              "query" : "手机",
              "operator" : "OR",
              "prefix_length" : 0,
              "max_expansions" : 50,
              "fuzzy_transpositions" : true,
              "lenient" : false,
              "zero_terms_query" : "NONE",
              "auto_generate_synonyms_phrase_query" : true,
              "boost" : 1.0
            }
          }
        },
        "weight" : 10.0
      },
      {
        "filter" : {
          "match" : {
            "subTitle" : {
              "query" : "手机",
              "operator" : "OR",
              "prefix_length" : 0,
              "max_expansions" : 50,
              "fuzzy_transpositions" : true,
              "lenient" : false,
              "zero_terms_query" : "NONE",
              "auto_generate_synonyms_phrase_query" : true,
              "boost" : 1.0
            }
          }
        },
        "weight" : 5.0
      }
    ],
    "score_mode" : "sum",
    "max_boost" : 3.4028235E38,
    "min_score" : 5.0,
    "boost" : 1.0
  }
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/700615.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号