谷粒商城学习笔记,第六天:ES全文检索+SpringBoot
协议 | 方式 | 描述 |
---|---|---|
9300 TCP | spring-data-elasticsearch:transport-api | ES7.X不建议使用,ES8准备启用 |
9200 HTTP | JestClient | 非官方,更新慢 |
9200 HTTP | RestTemplate | ES很多操作需要自己封装,麻烦 |
9200 HTTP | HttpClient | ES很多操作需要自己封装,麻烦 |
9200 HTTP | Elasticsearch-Rest-Client | 官方RestClient,封装了ES操作,API层次分明,上手简单 |
一、Elasticsearch-Rest-Client
elasticsearch-rest-hight-level-client
1、引入POM
<!--导入ES-->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.4.2</version>
</dependency>
2、修改elasticsearch版本
##由于springboot2.3.5.RELEASE默认引用的是elasticsearch7.6.2版本的
##我们的服务器是7.4.2版本的,我们在配置中替换springboot自带的版本
<properties>
<!--替换springboot自带版本-->
<elasticsearch.version>7.4.2</elasticsearch.version>
</properties>
3、配置ES
@Configuration
public class ElasticSearchConfig {
@Bean
public RestHighLevelClient restHighLevelClient(){
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("182.92.191.49", 9200, "http")
));
return client;
}
}
4、测试
@SpringBootTest
public class GulimallSearchApplicationTests {
@Autowired
private RestHighLevelClient client;
@Test
public void contextLoads() {
System.out.println(client);
}
}
5、RequestOptions
RequestOptions类保留应在同一应用程序中的许多请求之间共享的部分请求。 您可以创建一个单例实例并在所有请求之间共享它:
-
添加所有请求所需的header
-
自定义响应的消费者等
可以在配置文件ElasticSearchConfig 添加如下:
public static final RequestOptions COMMON_OPTIONS;
static {
RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
//TODO
COMMON_OPTIONS = builder.build();
}
6、添加数据和修改数据
@Autowired
private RestHighLevelClient client;
@Data
class User{
private String username;
private Integer age;
private String gender;
}
@Test
public void addAndEditIndex() throws IOException {
//封装请求body
IndexRequest request = new IndexRequest("users");
User user = new User();
user.setUsername("lee");
user.setAge(18);
user.setGender("Male");
String userJson = JSON.toJSONString(user);
request.source(userJson, XContentType.JSON);
//发送请求
IndexResponse resp = client.index(request, ElasticSearchConfig.COMMON_OPTIONS);
System.out.println(resp);
}
7、查询数据
@Data
@ToString
static class BankData {
private int account_number;
private int balance;
private String firstname;
private String lastname;
private int age;
private String gender;
private String address;
private String employer;
private String email;
private String city;
private String state;
}
//搜索address中包含mill的所有人的年龄分布以及这个年龄段的平均薪资
@Test
public void searchIndex() throws IOException {
//创建搜索请求
SearchRequest searchRequest = new SearchRequest();
//指定索引
searchRequest.indices("bank");
//创建检索语句
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//搜索:QueryBuilders是搜索的工具类
sourceBuilder.query(QueryBuilders.matchQuery("address","mill"));
//聚合: AggregationBuilders是聚合的工具类
TermsAggregationBuilder ageTermsAggs = AggregationBuilders.terms("ageTermsAggs").field("age");
AvgAggregationBuilder balanceAvgAggs = AggregationBuilders.avg("balanceAvgAggs").field("balance");
ageTermsAggs.subAggregation(balanceAvgAggs);
sourceBuilder.aggregation(ageTermsAggs);
searchRequest.source(sourceBuilder);
System.out.println("检索语句:"+sourceBuilder.toString());
//发送请求
SearchResponse resp = client.search(searchRequest, ElasticSearchConfig.COMMON_OPTIONS);
//处理请求结果
RestStatus status = resp.status();
System.out.println("检索结果status:"+status.toString());
//搜索结果
SearchHits hits = resp.getHits();
for (SearchHit hit : hits) {
String sourceString = hit.getSourceAsString();
BankData bankData = JSONObject.parseObject(sourceString, BankData.class);
System.out.println("检索结果body:"+bankData);
}
//聚合结果
Aggregations aggs = resp.getAggregations();
Terms ageTermsAggsResult = aggs.get("ageTermsAggs");
for (Terms.Bucket bucket : ageTermsAggsResult.getBuckets()) {
String key = bucket.getKey().toString();
long docCount = bucket.getDocCount();
System.out.println("检索结果aggs 年龄分布:"+key+" "+docCount);
Aggregations subAggs = bucket.getAggregations();
Avg balanceAvgAggsResult = subAggs.get("balanceAvgAggs");
System.out.println("检索结果aggs 所属年龄的 平均薪资:"+balanceAvgAggsResult.getValue());
}
}
打印内容:
检索语句:{"query":{"match":{"address":{"query":"mill","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}}},"aggregations":{"ageTermsAggs":{"terms":{"field":"age","size":10,"min_doc_count":1,"shard_min_doc_count":0,"show_term_doc_count_error":false,"order":[{"_count":"desc"},{"_key":"asc"}]},"aggregations":{"balanceAvgAggs":{"avg":{"field":"balance"}}}}}}
检索结果status:OK
检索结果body:GulimallSearchApplicationTests.BankData(account_number=970, balance=19648, firstname=Forbes, lastname=Wallace, age=28, gender=M, address=990 Mill Road, employer=Pheast, email=forbeswallace@pheast.com, city=Lopezo, state=AK)
检索结果body:GulimallSearchApplicationTests.BankData(account_number=136, balance=45801, firstname=Winnie, lastname=Holland, age=38, gender=M, address=198 Mill Lane, employer=Neteria, email=winnieholland@neteria.com, city=Urie, state=IL)
检索结果body:GulimallSearchApplicationTests.BankData(account_number=345, balance=9812, firstname=Parker, lastname=Hines, age=38, gender=M, address=715 Mill Avenue, employer=Baluba, email=parkerhines@baluba.com, city=Blackgum, state=KY)
检索结果body:GulimallSearchApplicationTests.BankData(account_number=472, balance=25571, firstname=Lee, lastname=Long, age=32, gender=F, address=288 Mill Street, employer=Comverges, email=leelong@comverges.com, city=Movico, state=MT)
检索结果aggs 年龄分布:38 2
检索结果aggs 所属年龄的 平均薪资:27806.5
检索结果aggs 年龄分布:28 1
检索结果aggs 所属年龄的 平均薪资:19648.0
检索结果aggs 年龄分布:32 1
检索结果aggs 所属年龄的 平均薪资:25571.0
二、ES数组的扁平化处理
注:数组里边是对象的时候需要注意,若数组里边是基础数据没关系
Elasticsearch 鼓励你在创建索引的时候就 扁平化 你的数据,这样做可以获取最好的搜索性能。在每一篇文档里面冗余一些数据可以避免join操作。
PUT users/_doc/1
{
"group" : "fans",
"user" : [
{
"first" : "John",
"last" : "Smith"
},
{
"first" : "Alice",
"last" : "White"
}
]
}
##ES会存储成如下结构:
{
"group" : "fans",
"user.first" : [ "alice", "john" ],
"user.last" : [ "smith", "white" ]
}
##然后我们搜索的时候回出现 alice smith的结果。
GET users/_search
{
"query": {
"bool": {
"must": [
{ "match": { "user.first": "Alice" }},
{ "match": { "user.last": "Smith" }}
]
}
}
}
为了避免数据被扁平化:
##为避免带有Object的数组 在存储的时候被扁平化,我们需要添加Nested
PUT users
{
"mappings": {
"properties": {
"user": {
"type": "nested"
}
}
}
}
PUT users/_doc/1
{
"group" : "fans",
"user" : [
{
"first" : "John",
"last" : "Smith"
},
{
"first" : "Alice",
"last" : "White"
}
]
}
三、商城中ES商品模型
商城中,商品上架SKU在ES中的模型
ES存储模型:
PUT product
{
"mappings": {
"properties": {
"skuId": {
"type": "long"
},
"spuId": {
"type": "keyword"
},
"skuTitle": {
"type": "text",
"analyzer": "ik_smart"
},
"skuPrice": {
"type": "keyword"
},
"skuImg": {
"type": "keyword",
"index": false,
"doc_values": false
},
"saleCount": {
"type": "long"
},
"hasStock": {
"type": "boolean"
},
"hotScore": {
"type": "long"
},
"brandId": {
"type": "long"
},
"catelogId": {
"type": "long"
},
"brandName": {
"type": "keyword",
"index": false,
"doc_values": false
},
"brandImg": {
"type": "keyword",
"index": false,
"doc_values": false
},
"catelogName": {
"type": "keyword",
"index": false,
"doc_values": false
},
"attrs": {
"type": "nested", ##嵌套,防止数组扁平化
"properties": {
"attrId": {
"type": "long"
},
"attrName": {
"type": "keyword",
"index": false,
"doc_values": false
},
"attrValue": {
"type": "keyword"
}
}
}
}
}
}
对应的JavaBean:
@Data
public class SkuEsModel {
//商品ID
private Long spuId;
//sku_id
private Long skuId;
//标题
private String skuTitle;
//价格
private BigDecimal skuPrice;
//图片
private String skuImg;
//销售量
private Long saleCount;
//是否还有库存
private Boolean hasStock;
//热度评分
private Long hotScore;
//品牌ID
private Long brandId;
//品牌名
private String brandName;
//品牌图片
private String brandImg;
//分类ID
private Long catalogId;
//分类名
private String catalogName;
//属性
private List<AttrsEsModel> attrs;
}
@Data
public class AttrsEsModel {
//属性ID
private Long attrId;
//属性名
private String attrName;
//属性值
private String attrValue;
}