一.使用 Kibana 操作 ES

下载 Kibana 镜像

docker pull kibana:7.9.3

启动 Kibana 容器

docker run \
-d \
--name kibana \
--net es-net \
-p 5601:5601 \
-e ELASTICSEARCH_HOSTS='["http://node1:9200","http://node2:9200","http://node3:9200"]' \
--restart=always \
kibana:7.9.3

启动后,浏览器访问 Kibana,进入 Dev Tools:

索引、分片和副本

索引

Elasticsearch索引用来存储我们要搜索的数据,以倒排索引结构进行存储。

例如,要搜索商品数据,可以创建一个商品数据的索引,其中存储着所有商品的数据,供我们进行搜索:


当索引中存储了大量数据时,大量的磁盘io操作会降低整体搜索新能,这时需要对数据进行分片存储。

索引分片

在一个索引中存储大量数据会造成性能下降,这时可以对数据进行分片存储。

每个节点上都创建一个索引分片,把数据分散存放到多个节点的索引分片上,减少每个分片的数据量来提高io性能:

每个分片都是一个独立的索引,数据分散存放在多个分片中,也就是说,每个分片中存储的都是不同的数据。搜索时会同时搜索多个分片,并将搜索结果进行汇总。

如果一个节点宕机分片不可用,则会造成部分数据无法搜索:

为了解决这一问题,可以对分片创建多个副本来解决。

索引副本

对分片创建多个副本,那么即使一个节点宕机,其他节点中的副本分片还可以继续工作,不会造成数据不可用:

分片的工作机制:

1.主分片的数据会复制到副本分片
2.搜索时,以负载均衡的方式工作,提高处理能力
3.主分片宕机时,其中一个副本分片会自动提升为主分片

下面我们就以上图的结构来创建 products 索引

创建索引

创建一个名为 products 的索引,用来存储商品数据。

分片和副本参数说明:

number_of_shards:分片数量,默认值是 5
number_of_replicas:副本数量,默认值是 1
我们有三个节点,在每个节点上都创建一个分片。每个分片在另两个节点上各创建一个副本。

# 创建索引,命名为 products
PUT /products
{"settings": {"number_of_shards": 3, "number_of_replicas": 2}
}

用索引名称过滤,查看 products 索引:

粗框为主分片,细框为副本分片

映射(数据结构)

类似于数据库表结构,索引数据也被分为多个数据字段,并且需要设置数据类型和其他属性。

映射,是对索引中字段结构的定义和描述。

字段的数据类型

常用类型:

数字类型:

  • byte、short、integer、long

  • float、double

  • unsigned_long
    字符串类型:

  • text : 会进行分词

  • keyword : 不会进行分词,适用于email、主机地址、邮编等
    日期和时间类型:

  • date

类型参考:

创建映射

在 products 索引中创建映射。

分词器设置:

  • analyzer:在索引中添加文档时,text类型通过指定的分词器分词后,再插入倒排索引
  • search_analyzer:使用关键词检索时,使用指定的分词器对关键词进行分词
    查询时,关键词优先使用 search_analyzer 设置的分词器,如果 search_analyzer 不存在则使用 analyzer 分词器。
# 定义mapping,数据结构
PUT /products/_mapping
{"properties": {"id": {"type": "long"},"title": {"type": "text","analyzer": "ik_max_word","search_analyzer": "ik_smart"},"category": {"type": "text","analyzer": "ik_smart","search_analyzer": "ik_smart"},"price": {"type": "float"},"city": {"type": "text","analyzer": "ik_smart","search_analyzer": "ik_smart"},"barcode": {"type": "keyword"}}
}

映射参考:

查看映射

GET /products/_mapping

添加文档

添加的文档会有一个名为_id的文档id,这个文档id可以自动生成,也可以手动指定,通常可以使用数据的id作为文档id。

# 添加文档
PUT /products/_doc/10033
{"id":"10033","title":"SONOS PLAY:5(gen2) 新一代PLAY:5无线智能音响系统 WiFi音箱家庭,潮酷数码会场","category":"潮酷数码会场","price":"3980.01","city":"上海","barcode":"527848718459"
}PUT /products/_doc/10034
{"id":"10034","title":"天猫魔盒 M13网络电视机顶盒 高清电视盒子wifi 64位硬盘播放器","category":"潮酷数码会场","price":"398.00","city":"浙江杭州","barcode":"522994634119"
}PUT /products/_doc/10035
{"id":"10035","title":"BOSE SoundSport耳塞式运动耳机 重低音入耳式防脱降噪音乐耳机","category":"潮酷数码会场","price":"860.00","city":"浙江杭州","barcode":"526558749068"
}PUT /products/_doc/10036
{"id":"10036","title":"【送支架】Beats studio Wireless 2.0无线蓝牙录音师头戴式耳机","category":"潮酷数码会场","price":"2889.00","city":"上海","barcode":"37147009748"
}PUT /products/_doc/10037
{"id":"10037","title":"SONOS PLAY:1无线智能音响系统 美国原创WiFi连接 家庭桌面音箱","category":"潮酷数码会场","price":"1580.01","city":"上海","barcode":"527783392239"
}

也可以自动生成 _id 值:

POST /products/_doc
{"id":"10027","title":"vivo X9前置双摄全网通4G美颜自拍超薄智能手机大屏vivox9","category":"手机会场","price":"2798.00","city":"广东东莞","barcode":"541396973568"
}

查看文档:

GET /products/_doc/10037

查看指定文档title字段的分词结果:

GET /products/_doc/10037/_termvectors?fields=title

修改文档

底层索引数据无法修改,修改数据实际上是先删除再重新添加。

两种修改方式:

PUT:对文档进行完整的替换
POST:可以修改一部分字段

修改价格字段的值:

# 修改文档 - 替换
PUT /products/_doc/10037
{"id":"10037","title":"SONOS PLAY:1无线智能音响系统 美国原创WiFi连接 家庭桌面音箱","category":"潮酷数码会场","price":"9999.99","city":"上海","barcode":"527783392239"
}

查看文档:

GET /products/_doc/10037

修改价格和城市字段的值:

# 修改文档 - 更新部分字段
POST /products/_update/10037
{"doc": {"price":"8888.88","city":"深圳"}
}

查看文档:

GET /products/_doc/10037

删除文档

DELETE /products/_doc/10037

清空

POST /products/_delete_by_query
{"query": {"match_all": {}}
}

删除索引

# 删除 products 索引
DELETE /products

可以尝试用不同的分片和副本值来重新创建 products 索引

二.搜索

导入测试数据

为了测试搜索功能,我们首先导入测试数据,3160条商品数据,数据样例如下

{ "index": {"_index": "pditems", "_id": "536563"}}
{ "id":"536563","brand":"联想","title":"联想(Lenovo)小新Air13 Pro 13.3英寸14.8mm超轻薄笔记本电脑","sell_point":"清仓!仅北京,武汉仓有货!","price":"6688.0","barcode":"","image":"/images/server/images/portal/air13/little4.jpg","cid":"163","status":"1","created":"2015-03-08 21:33:18","updated":"2015-04-11 20:38:38"}

下载测试数据

将压缩文件中的 pditems.json 上传到服务器

创建索引和映射

PUT /pditems
{"settings": {"number_of_shards": 3, "number_of_replicas": 2},"mappings": {"properties": {"id": {"type": "long"},"brand": {"type": "text","analyzer": "ik_smart"},"title": {"type": "text","analyzer": "ik_max_word"},"sell_point": {"type": "text","analyzer": "ik_max_word","search_analyzer": "ik_smart"},"price": {"type": "float"},"image": {"type": "keyword"},"cid": {"type": "long"},"status": {"type": "byte"},"created": {"type": "date","format": "yyyy-MM-dd HH:mm:ss"},"updated": {"type": "date","format": "yyyy-MM-dd HH:mm:ss"}} }
}

用 head 查看索引:

导入数据

在服务器上,进入 pditems.json 所在的文件夹,执行批量数据导入:

curl -XPOST 'localhost:9200/pditems/_bulk' \-H 'Content-Type:application/json' \--data-binary @pditems.json

查看数据

搜索 pditems 索引中全部 3160 条数据:

GET /pditems/_search
{"query": {"match_all": {}},"size": 3160
}

搜索文档

搜索所有数据

# 搜索 pditems 索引中全部数据
POST /pditems/_search
{"query": {"match_all": {}}
}

关键词搜索

# 查询 pditems 索引中title中包含"电脑"的商品
POST /pditems/_search
{"query": {"match": {"title": "电脑"}}
}

搜索结果过滤器

# 价格大于2000,并且title中包含"电脑"的商品
POST /pditems/_search
{"query": {"bool": {"must": [{"match": {"title": "电脑"}}],"filter": [{"range": {"price": {"gte": "2000"}}}]}}
}

搜索结果高亮显示

em标签高亮
highlight高亮设置
multi_match多字段匹配

POST /pditems/_search
{"query": {"multi_match":{"query": "手机","fields": ["title", "sell_point"]}},"highlight" : {"pre_tags" : ["<i class=\"highlight\">"],"post_tags" : ["</i>"],"fields" : {"title" : {},"sell_point" : {"pre_tags": "<em>","post_tags": "</em>"}}}
}

三.Spring Data Elasticsearch - 增删

Spring Data Elasticsearch

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

Spring Data Elasticsearch 是 Elasticsearch 搜索引擎开发的解决方案。它提供:

模板对象,用于存储、搜索、排序文档和构建聚合的高级API。

例如,Repository 使开发者能够通过定义具有自定义方法名称的接口来表达查询。

案例说明

在 Elasticsearch 中存储学生数据,并对学生数据进行搜索测试。

数据结构:


案例测试以下数据操作:

1.创建 students 索引和映射
2.C - 创建学生数据
3.R - 访问学生数据
4.U - 修改学生数据
5.D - 删除学生数据
6.使用 Repository 和 Criteria 搜索学生数据

创建项目

1.新建工程


2.新建 springboot module,添加 spring data elasticsearch 依赖




3.项目的 pom.xml 文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.6.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>cn.tedu</groupId><artifactId>es-springboot</artifactId><version>0.0.1-SNAPSHOT</version><name>es-springboot</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

application.yml 配置

logging.level.tracer=TRACE 作用是在控制台中显示底层的查询日志

spring:elasticsearch:rest:uris: http://192.168.64.181:9200logging:level:tracer: TRACE

Student 实体类

package cn.tedu.es.entity;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;// spring data es API 可以根据这里的设置
// 在服务器新建索引
// 一般情况下,索引应该自己在服务器上手动创建
@Document(indexName = "students",shards = 3,replicas = 2)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {@Id // 使用学号作为索引idprivate Long id;private String name;private Character gender;@Field("birthDate")  // es索引中的字段名,与变量名相同可以省略private String birthDate;
}

@Document 注解

@Documnet注解对索引的参数进行设置。

上面代码中,把 students 索引的分片数设置为3,副本数设置为2。

@Id 注解

在 Elasticsearch 中创建文档时,使用 @Id 注解的字段作为文档的 _id 值

@Field 注解

通过 @Field 注解设置字段的数据类型和其他属性。

文本类型 text 和 keyword

text 类型会进行分词。

keyword 不会分词。

analyzer 指定分词器

通过 analyzer 设置可以指定分词器,例如 ik_smart、ik_max_word 等。

我们这个例子中,对学生姓名字段使用的分词器是 ngram 分词器,其分词效果如下面例子所示:

通过 ElasticsearchRepository 实现 CRUD 操作

Spring Data 的 Repository 接口提供了一种声明式的数据操作规范,无序编写任何代码,只需遵循 Spring Data 的方法定义规范即可完成数据的 CRUD 操作。

ElasticsearchRepository 继承自 Repository,其中已经预定义了基本的 CURD 方法,我们可以通过继承 ElasticsearchRepository,添加自定义的数据操作方法。

Repository 方法命名规范

自定义数据操作方法需要遵循 Repository 规范,示例如下:

关键词 方法名 es查询
And findByNameAndPrice { “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “?”, “fields” : [ “name” ] } }, { “query_string” : { “query” : “?”, “fields” : [ “price” ] } } ] } }}
Or findByNameOrPrice { “query” : { “bool” : { “should” : [ { “query_string” : { “query” : “?”, “fields” : [ “name” ] } }, { “query_string” : { “query” : “?”, “fields” : [ “price” ] } } ] } }}
Is findByName { “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “?”, “fields” : [ “name” ] } } ] } }}
Not findByNameNot { “query” : { “bool” : { “must_not” : [ { “query_string” : { “query” : “?”, “fields” : [ “name” ] } } ] } }}
Between findByPriceBetween { “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : ?, “to” : ?, “include_lower” : true, “include_upper” : true } } } ] } }}
LessThan findByPriceLessThan { “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : null, “to” : ?, “include_lower” : true, “include_upper” : false } } } ] } }}
LessThanEqual findByPriceLessThanEqual { “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : null, “to” : ?, “include_lower” : true, “include_upper” : true } } } ] } }}
GreaterThan findByPriceGreaterThan { “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : ?, “to” : null, “include_lower” : false, “include_upper” : true } } } ] } }}
GreaterThanEqual findByPriceGreaterThan { “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : ?, “to” : null, “include_lower” : true, “include_upper” : true } } } ] } }}
Before findByPriceBefore { “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : null, “to” : ?, “include_lower” : true, “include_upper” : true } } } ] } }}
After findByPriceAfter { “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : ?, “to” : null, “include_lower” : true, “include_upper” : true } } } ] } }}
Like findByNameLike { “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “?*”, “fields” : [ “name” ] }, “analyze_wildcard”: true } ] } }}
StartingWith findByNameStartingWith { “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “?*”, “fields” : [ “name” ] }, “analyze_wildcard”: true } ] } }}
EndingWith findByNameEndingWith { “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “*?”, “fields” : [ “name” ] }, “analyze_wildcard”: true } ] } }}
Contains/Containing findByNameContaining { “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “?”, “fields” : [ “name” ] }, “analyze_wildcard”: true } ] } }}
In (when annotated as FieldType.Keyword) findByNameIn(Collectionnames) { “query” : { “bool” : { “must” : [ {“bool” : {“must” : [ {“terms” : {“name” : ["?","?"]}} ] } } ] } }}
In findByNameIn(Collectionnames) { “query”: {“bool”: {“must”: [{“query_string”:{“query”: “”?" “?”", “fields”: [“name”]}}]}}}
NotIn (when annotated as FieldType.Keyword) findByNameNotIn(Collectionnames) { “query” : { “bool” : { “must” : [ {“bool” : {“must_not” : [ {“terms” : {“name” : ["?","?"]}} ] } } ] } }}
NotIn findByNameNotIn(Collectionnames) {“query”: {“bool”: {“must”: [{“query_string”: {“query”: “NOT(”?" “?”)", “fields”: [“name”]}}]}}}
Near findByStoreNear Not Supported Yet !
True findByAvailableTrue { “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “true”, “fields” : [ “available” ] } } ] } }}
False findByAvailableFalse { “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “false”, “fields” : [ “available” ] } } ] } }}
OrderBy findByAvailableTrueOrderByNameDesc { “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “true”, “fields” : [ “available” ] } } ] } }, “sort”:[{“name”:{“order”:“desc”}}] }

StudentRepository

package cn.tedu.esspringboot.es;import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import java.util.List;public interface StudentRepository extends ElasticsearchRepository<Student, Long> {List<Student> findByName(String name);List<Student> findByNameOrBirthDate(String name, String birthDate);
}

业务类 StudentService

package cn.tedu.esspringboot.es;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class StudentService {@Autowiredprivate StudentRepository studentRepo;public void save(Student student) {studentRepo.save(student);}public void delete(Long id) {studentRepo.deleteById(id);}public void update(Student student) {save(student);}public List<Student> findByName(String name) {return studentRepo.findByName(name);}public List<Student> findByNameOrBirthDate(String name, String birthDate) {return studentRepo.findByNameOrBirthDate(name, birthDate);}
}

在 Elasticsearch 中创建 students 索引

在开始运行测试之前,在 Elasticsearch 中先创建 students 索引:

PUT /students
{"settings": {"number_of_shards": 3,"number_of_replicas": 2,"index.max_ngram_diff":30,"analysis": {"analyzer": {"ngram_analyzer": {"tokenizer": "ngram_tokenizer"}},"tokenizer": {"ngram_tokenizer": {"type": "ngram","min_gram": 1,"max_gram": 30,"token_chars": ["letter","digit"]}}}},"mappings": {"properties": {"id": {"type": "long"},"name": {"type": "text","analyzer": "ngram_analyzer"},"gender": {"type": "keyword"},"birthDate": {"type": "date","format": "yyyy-MM-dd"}}}
}

测试学生数据的 CRUD 操作

添加测试类,对学生数据进行 CRUD 测试

package cn.tedu.esspringboot;import cn.tedu.esspringboot.es.Student;
import cn.tedu.esspringboot.es.StudentService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.util.List;@SpringBootTest
public class Test1 {@Autowiredprivate StudentService studentService;@Testpublic void test1() {studentService.save(new Student(998L,"张三",'男',"2020-12-04"));}@Testpublic void test2() {studentService.update(new Student(1L,"李四",'女',"2020-12-04"));}@Testpublic void test3() {List<Student> stu = studentService.findByName("四");System.out.println(stu);}@Testpublic void test4() throws Exception {List<Student> stu;stu = studentService.findByNameOrBirthDate("四", "1999-09-09");System.out.println(stu);stu = studentService.findByNameOrBirthDate("SFSDFS", "2020-12-04");System.out.println(stu);}
}

依次运行每个测试方法,并使用 head 观察测试结果

使用 Criteria 构建查询

Spring Data Elasticsearch 中,可以使用 SearchOperations 工具执行一些更复杂的查询,这些查询操作接收一个 Query 对象封装的查询操作。

Spring Data Elasticsearch 中的 Query 有三种:

  • CriteriaQuery
  • StringQuery
  • NativeSearchQuery
    多数情况下,CriteriaQuery 都可以满足我们的查询求。下面来看两个 Criteria 查询示例:

StudentSearcher

package cn.tedu.esspringboot.es;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.stereotype.Component;import java.util.List;
import java.util.stream.Collectors;@Component
public class StudentSearcher {@Autowiredprivate ElasticsearchOperations searchOperations;public List<Student> searchByBirthDate(String birthDate) {Criteria c = new Criteria("birthDate").is(birthDate);return criteriaSearch(c);}public List<Student> searchByBirthDate(String ge, String le) {Criteria c = new Criteria("birthDate").between(ge, le);return criteriaSearch(c);}private List<Student> criteriaSearch(Criteria c) {CriteriaQuery q = new CriteriaQuery(c);SearchHits<Student> hits = searchOperations.search(q, Student.class);List<Student> list = hits.stream().map(SearchHit::getContent).collect(Collectors.toList());return list;}
}

修改 StudentService

在 StudentService 中,调用 StudentSearcher,执行查询:

package cn.tedu.esspringboot.es;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class StudentService {@Autowiredprivate StudentRepository studentRepo;@Autowiredprivate StudentSearcher studentSearcher;public void save(Student student) {studentRepo.save(student);}public void delete(Long id) {studentRepo.deleteById(id);}public void update(Student student) {save(student);}public List<Student> findByName(String name) {return studentRepo.findByName(name);}public List<Student> findByNameOrBirthDate(String name, String birthDate) {return studentRepo.findByNameOrBirthDate(name, birthDate);}public List<Student> findByBirthDate(String birthDate) {return studentSearcher.searchByBirthDate(birthDate);}public List<Student> findByBirthDate(String ge, String le) {return studentSearcher.searchByBirthDate(ge, le);}
}

在测试类中添加测试方法


package cn.tedu.esspringboot;import cn.tedu.esspringboot.es.Student;
import cn.tedu.esspringboot.es.StudentService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.util.List;@SpringBootTest
public class Test1 {@Autowiredprivate StudentService studentService;@Testpublic void test1() {studentService.save(new Student(998L,"张三",'男',"2020-12-04"));}@Testpublic void test2() {studentService.update(new Student(1L,"李四",'女',"2020-12-04"));}@Testpublic void test3() {List<Student> stu = studentService.findByName("四");System.out.println(stu);}@Testpublic void test4() throws Exception {List<Student> stu;stu = studentService.findByNameOrBirthDate("四", "1999-09-09");System.out.println(stu);stu = studentService.findByNameOrBirthDate("SFSDFS", "2020-12-04");System.out.println(stu);}@Testpublic void test5() throws Exception {List<Student> stu;stu = studentService.findByBirthDate("2020-12-04");System.out.println(stu);}@Testpublic void test6() throws Exception {List<Student> stu;stu = studentService.findByBirthDate("2020-12-05", "2020-12-09");System.out.println(stu);}
}

四.拼多商城商品搜索+高亮

1. 添加 spring data es 依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId></dependency>

2. yml 配置 es 服务器地址

spring:elasticsearch:rest:uris:- http://192.168.64.181:9200- http://192.168.64.181:9201- http://192.168.64.181:9202

3. 新建实体类 Item,封装从 es 搜索的数据

package com.pd.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;@Document(indexName = "pditems")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Item {@Idprivate Long id;private String brand;private String title;@Field("sell_point")private String sellPoint;private String price;private String image;
}

4. 新建 ItemRepository 接口

package com.pd.es;import com.pd.pojo.Item;import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.annotations.Highlight;
import org.springframework.data.elasticsearch.annotations.HighlightField;
import org.springframework.data.elasticsearch.annotations.HighlightParameters;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;import java.util.List;/*** 做高亮显示*/
public interface ItemRepository extends ElasticsearchRepository<Item,Long> {/*** 如果要做高亮显示,高亮结果会封装到SearchHit对象* @param kye1* @param key2* @param pageable* @return*/@Highlight(parameters = @HighlightParameters(preTags = "<em>",postTags = "</em>"),fields = {@HighlightField(name="title"),@HighlightField(name = "sellPoint")})List<SearchHit<Item>> findByTitleOrSellPoint(String kye1, String key2, Pageable pageable);
}

5. 添加搜索方法: findByTitleOrSellPoint()

6. SearchService

package com.pd.service;
import com.pd.pojo.Item;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.SearchHit;import java.util.List;public interface SearchService {List<SearchHit<Item>> search(String key, Pageable pageable);
}

7. SearchController

package com.pd.controller;import com.pd.pojo.Item;
import com.pd.service.SearchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;import java.util.ArrayList;
import java.util.List;@Controller
public class SearchController {@Autowiredprivate SearchService searchService;@GetMapping("/search/toSearch.html") // ?key=手机&page=0&size=20public String search(Model model, String key, Pageable pageable) {List<SearchHit<Item>> r = searchService.search(key, pageable);// 把所有 SearchHit 中的 Item 对象拿出来,放入一个新的 List<Item> 集合List<Item> list = new ArrayList<>();for (SearchHit<Item> sh : r) {Item item = sh.getContent();//从 SearchHit 取出上商品对象// SearchHit 对象中的高亮数据// ["xxx", "<em>", "手机", "</em>", "xxxxx"]List<String> titleHighlight = sh.getHighlightField("title");// 把高亮的 title 放入 item,替换原始的 titleitem.setTitle(highlightTiele(titleHighlight));list.add(item);}// 集合放入model对象,传递到 jsp 界面进行显示model.addAttribute("list", list);model.addAttribute("p", pageable);return "/search.jsp";}private String highlightTiele(List<String> titleHighlight) {StringBuilder sb = new StringBuilder();for (String s : titleHighlight) {sb.append(s);}return sb.toString();}
}

8. search.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"pageEncoding="utf-8"%><%@ taglib prefix="c"uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page isELIgnored="false" %>
<!DOCTYPE html>
<html>
<head lang="en"><meta charset="UTF-8"><title>商品搜索页面</title><link rel="stylesheet" href="../css/header.css" /><link rel="stylesheet" href="../css/search.css" /><link rel="stylesheet" href="../css/footer.css" /><style>div.describe p em {color: #f00;}</style></head>
<jsp:include page="commons/header.jsp"></jsp:include>
<body>
<div class="big"><form name="" action="" method="post"><section id="section"><p class="header"> 搜索结果 > ${param.key} </p><div id="content_box"><%--   ${list} 从Model获取list属性:List<Item>   --%><c:forEach items="${list}" var="solrItem"><div class="lf" id="d1"><div class="img"><!-- ../images/search/product_img.png --><img src="${solrItem.image}" alt="" onclick="toItemInfo(${solrItem.id})" /></div><div class="describe"><p onclick="toItemInfo(${solrItem.id})">${solrItem.title}</p><span class="price"><b>¥</b><span class="priceContent">${solrItem.price}</span></span><span class="addCart"><img id="collect" src="../images/search/care.png" alt="" /><a href="javascript:void(0);" class="add_cart">加入购物车</a></span><!--<span class="succee" style="display: none"><img src="/images/search/product_true.png" alt="" /><span>已移入购物车</span></span>--></div></div></c:forEach></div><c:if test="${list.size() == 0}">没有更多商品了!</c:if><c:if test="${p.pageNumber > 0}"><a href="?key=${param.key}&page=${p.pageNumber-1}&size=${p.pageSize}">上一页</a></c:if><c:if test="${list.size() != 0}"><a href="?key=${param.key}&page=${p.pageNumber+1}&size=${p.pageSize}">下一页</a></c:if></section></form>
</div>
<!-- 尾部-->
<!-- 页面底部-->
<div class="foot_bj"><div id="foot"><div class="lf"><p class="footer1"><img src="../images/footer/logo.png" alt="" class=" footLogo"/></p><p class="footer2"><img src="../images/footer/footerFont.png"alt=""/></p></div><div class="foot_left lf" ><ul><li><a href="#"><h3>买家帮助</h3></a></li><li><a href="#">新手指南</a></li><li><a href="#">服务保障</a></li><li><a href="#">常见问题</a></li></ul><ul><li><a href="#"><h3>商家帮助</h3></a></li><li><a href="#">商家入驻</a></li><li><a href="#">商家后台</a></li></ul><ul><li><a href="#"><h3>关于我们</h3></a></li><li><a href="#">关于拼多</a></li><li><a href="#">联系我们</a></li><li><img src="../images/footer/wechat.png" alt=""/><img src="../images/footer/sinablog.png" alt=""/></li></ul></div><div class="service"><p>拼多商城客户端</p><img src="../images/footer/ios.png" class="lf"><img src="../images/footer/android.png" alt="" class="lf"/></div><div class="download"><img src="../images/footer/erweima.png"></div><!-- 页面底部-备案号 #footer --><div class="record">&copy;2017 拼多集团有限公司 版权所有 京ICP证xxxxxxxxxxx</div></div>
</div>
<div class="modal" style="display:none"><div class="modal_dialog"><div class="modal_header">操作提醒</div><div class="modal_information"><img src="../images/model/model_img2.png" alt=""/><span>将您的宝贝加入购物车?</span></div><div class="yes"><span>确定</span></div><div class="no"><span>取消</span></div></div>
</div>
<script src="../js/jquery-3.1.1.min.js"></script>
<script src="../js/index.js"></script>
<script src="../js/jquery.page.js"></script>
<script>$(".add_cart").click(function(){$(".modal").show();$(".modal .modal_information span").html("将您的宝贝加入购物车?");})$(".yes").click(function(){$(".modal").hide();})$('.no').click(function(){$('.modal').hide();})
</script>
<!--<script type="text/javascript">// var status = ${status};var pages = ${pageBean.totalPages};var index = ${pageBean.pageIndex};$(".tcdPageCode").createPage({// 总页数pageCount:pages,// 起始页current:index,backFn:function(p){// 执行代码window.location.href="http://localhost:18888/search.html?q=${q}&page="+p;}});
</script>-->
<!--<script type="text/javascript">/* 商品详情页  */function toItemInfo(id) {if (id) {window.location.href="/toItemInfo/"+id+".html";}else {alert("商品id不存在");}}
</script>-->
<script type="text/javascript">/**添加到收藏**/$("#collect").click(function(e){$(".modal").show();$(".modal .modal_information span").html("将您的宝贝加入收藏夹");})$(".yes").click(function(){$(".modal").hide();$('#collect').attr("src","../images/search/care1.png");})
</script>
</body>
</html>

9.Header.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!-- 页面顶部-->
<header id="top"><div id="logo" class="lf"><a href="/"> <img   src="/images/server/images/portal/header/logo.png" alt="logo" /></a></div><div id="top_input" class="lf"><c:choose><c:when test="${not empty param.key}"><input id="input" type="text" value="${param.key}" /></c:when><c:otherwise><input id="input" type="text" placeholder="请输入您要搜索的内容" /></c:otherwise></c:choose><div class="seek" tabindex="-1"><div class="actived" ><span>分类搜索</span> <img src="/images/server/images/portal/header/header_normal.png" alt=""/></div><div class="seek_content" ><div id="shcy" >生活餐饮</div><div id="xxyp" >学习用品</div><div id="srdz" >私人订制</div></div></div><a href="javascript:void(0);" class="rt" onclick="search1()"><img id="search"src="/images/server/images/portal/header/search.png" alt="搜索"/></a></div><div class="rt"><ul class="lf" id="iul"><li><a href="/collect/toMyCollect.html" title="我的收藏"> <img class="care"src="/images/server/images/portal/header/care.png"alt="" /></a><b>|</b></li><li><a href="/order/toMyOrder.html" title="我的订单"> <img class="order"src="/images/server/images/portal/header/order.png" alt="" /></a><b>|</b></li><li><a href="/cart/toCart.html" title="我的购物车"> <img class="shopcar"src="/images/server/images/portal/header/shop_car.png" alt="" /></a><b>|</b></li><li></li></ul></div><br />
</header>
<nav id="nav"><ul><li><a href="/">首页</a></li><li><a href="/food/toItemFood.html">生活餐饮</a></li><li><a href="/toCate.html">学习用品</a></li><li><a href="/lookforward.html">私人定制</a></li></ul>
</nav>
<script src="/js/jquery-3.1.1.min.js"></script>
<script src="/js/slide.js"></script>
<script type="text/javascript">function logout() {$.ajax({url : '/user/logout.html',type : 'post',dataType:'json',success:function(result) {if (result != null && result != "" && result != undefined) {if (result.status == 200) {//alert(result.msg);window.location.href = "/user/toLogin.html";}else {alert(result.msg);}}},error:function() {alert('退出失败!');}});}
</script>
<script>$('#nav>ul>li').click(function(){$(this).children().addClass('active');$(this).siblings().children().removeClass('active');})
</script><script src="/js/jquery.cookie.js"></script>
<script type="text/javascript">$(function () {//请求本网站checkLogin.html,checkLogin()用httpClient做代理去访问sso$.ajax({type:"POST",url:"/user/checkLogin.html",xhrFields:{withCredentials:true},dataType:"json",success:function(result){var user = result.data;console.log(result);if (result.status === 200) {$("#iul").append('<li><a href="/lookforward.html">'+user.username+'</a><b>|</b></li><li><a href="/address/list.html">地址管理</a> | <a href="javascript:;" οnclick="logout()">退出</a></li>');}else if(result.status === 500){$("#iul").append('<li><a href="/user/toLogin.html">登录</a></li>');}},error:function(textStatus,XMLHttpRequest){//alert("系统异常!");}});//$.cookie出异常//var ticket = $.cookie("DN_TICKET");//服务器返回的是js,这种处理跨域的方式叫jsonp/* $.ajax({type:"post",url:"http://sso.ajstore.com:90/user/checkLoginForJsonp.html",dataType:"jsonp",jsonp:"jsonpCallback",//jsonpCallback是服务器端接收参数的参数名xhrFields:{withCredentials:true},//ajax默认不发送cookie//浏览器收到的是jquery(json字符串)//函数名jquery//执行函数jquery,得到的是json字符串,再调用success,把json字符串传过来了success:function(result){var user = result.data;console.log(result);if (result.status === 200) {$("#iul").append('<li><a href="/lookforward.html">'+user.username+'</a><b>|</b></li><li><a href="javascript:;" οnclick="logout()">退出</a></li>');}else if(result.status === 500){$("#iul").append('<li><a href="http://sso.ajstore.com:90/user/toLogin.html?callback=http://www.ajstore.com">登录</a></li>');}},error:function(textStatus,XMLHttpRequest){alert("系统异常!"+JSON.stringify(textStatus)+" ------ "+XMLHttpRequest);}}); *///服务器返回的是json/* $.ajax({type:"post",url:"http://sso.ajstore.com:90/user/checkLogin.html",dataType:"json",//原先是jsonp要改成jsonxhrFields:{withCredentials:true},//ajax默认不发送cookiesuccess:function(result){var user = result.data;console.log(result);if (result.status === 200) {$("#iul").append('<li><a href="/lookforward.html">'+user.username+'</a><b>|</b></li><li><a href="javascript:;" οnclick="logout()">退出</a></li>');}else if(result.status === 500){$("#iul").append('<li><a href="http://sso.ajstore.com:90/user/toLogin.html?callback=http://www.ajstore.com">登录</a></li>');}},error:function(textStatus,XMLHttpRequest){alert("系统异常!"+JSON.stringify(textStatus)+" ------ "+XMLHttpRequest);}}); */})
</script>
<script>function search1(){var q=$("#input").val();console.log(q);window.location.href = "/search/toSearch.html?key="+q;}
</script>
<script type="text/javascript">document.onkeydown=keyDownSearch;function keyDownSearch(e) {var theEvent = e || window.event;var code = theEvent.keyCode || theEvent.which || theEvent.charCode;if (code == 13) {search1();return false;}return true;}
</script>

10.测试

搜索出来高亮

Kibana 操作 ES+搜索相关推荐

  1. Elasticsearch(一)——Es安装(三个必安工具、安装各种类型分词器)、Es 十大核心概念、通过 Kibana 操作 Es(中文分词、Es各种索引命令操作)

    Elasticsearch(一)--Es安装(三个必安工具.安装各种类型分词器).Es 十大核心概念.通过 Kibana 操作 Es(中文分词.Es各种索引命令操作) 一.Elasticsearch ...

  2. elasticsearch 7.9.3知识归纳整理(二)之 es基本原理及使用kibana操作es的常见命令

    es基本原理及使用kibana操作es的常见命令 一.es的基本原理与基础概念 1.1 倒排索引 倒排索引源于实际应用中需要根据属性的值来查找记录.这种索引表中的每一项都包括一个属性值和具有该属性值的 ...

  3. Elasticsearch(三) 使用kibana 操作ES

    文档中包含语句 1,索引(新增 查询 删除) 2, mapping 创建 3,文档(新增,修改,删除,批量新增) 4,文档查询(基本查询,高级查询,分页,高亮,排序) 1,使用kibana 新增 查询 ...

  4. Elasticsearch(三)——Es搜索(简单使用、全文查询、复合查询)、地理位置查询、特殊查询、聚合操作、桶聚合、管道聚合

    Elasticsearch(三)--Es搜索(简单使用.全文查询.复合查询).地理位置查询.特殊查询.聚合操作.桶聚合.管道聚合 一.Es搜索 这里的 Es 数据博主自己上网找的,为了练习 Es 搜索 ...

  5. ELasticSearch安装使用过程中遇到的坑的解决方案,以及使用Kibana操作ELasticSearch

    一.安装elasticsearch和kibana 安装elasticsearch和kibana,我现在使用的是windows版本的,安装其实也不难,具体的安装教程可以参照这两篇博客,写的安装步骤也很详 ...

  6. docker安装es+mac安装Kibana工具+es查询语法笔记

    一.docker安装es 1.下载镜像 docker pull elasticsearch:7.9.0 下载完后,查看镜像 docker images ​​ 2.启动镜像 docker network ...

  7. 日志分析系统ELK之Kibana、es的替代metricbeat

    Kibana Kibana简介 怎么将数据导入kibana 演示环境 kibana安装与配置 可视化现有 Elasticsearch 索引中的数据 创建索引 创建可视化仪表盘图 创建可视化垂直条形图 ...

  8. Spring Boot操作ES进行各种高级查询(值得收藏)

    作者 | 后青春期的Keats 来源 | http://cnblogs.com/keatsCoder/p/11341835.html SpringBoot整合ES 创建SpringBoot项目,导入 ...

  9. SpringBoot高级-检索-SpringBoot整合Jest操作ES

    接下来就用SpringBoot来整合ElasticSearch进行测试,pom文件引入了spring-boot-starter-data-elasticsearch,其实加了data都是用spring ...

  10. springboot操作ES之ElasticSearch_EasyEs

    springboot操作ES之ElasticSearch_EasyEs 前置环境 es:7.x springboot:2.6.0 easyes:1.0.2 1.导入依赖 <dependency& ...

最新文章

  1. 2018-2019-1 20165206 《信息安全系统设计基础》第九周学习总结
  2. 专题 18 Inline Assembly(在C语言中嵌套使用汇编)
  3. jAVA 得到Map价值
  4. vmstat 命令的使用
  5. semantic ui要装什么才能使用
  6. Mac OS使用技巧之十三:Finder中标记的使用
  7. php strpo函数,php strpos函数有什么用
  8. php mongodb排序查询,Mongodb 如何按照内嵌文档的某个字段排序?
  9. 利器:服务器与CST时间误差8小时的修复方法——timedatectl
  10. 《java入门第一季》之java语法部分小案例
  11. 惠普OMEN游戏本驱动曝内核级漏洞,影响数百万Windows 计算机
  12. 打造机器人:为遥控小车加一个树莓派
  13. javascript代码混淆的原理
  14. 计算机毕业设计Java消防安全应急培训管理平台(源码+系统+mysql数据库+Lw文档)
  15. 双向迁移图协同过滤GCF的跨域推荐
  16. AR law : Privacy
  17. centos 7进入rescue模式
  18. java 高并发商城库存订单处理,下单减库存,如何解决高并发减库存问题
  19. Android animation呼吸动画 心形动画
  20. Java+JSP+MySQL基于SSM的医院挂号就诊系统

热门文章

  1. 诺基亚E71 专用UCWEB 6.7
  2. 韩信点兵python算法_韩信点兵算法流程图
  3. (转载)人工智能在围棋程序中的应用——复旦大学附属中学(施遥)
  4. 推荐一款2.5v 基准 电压源 芯片
  5. rtc驱动模型及rx8025驱动学习
  6. [转帖]超能课堂:Intel平台芯片组变迁史
  7. buuctf-misc部分wp(更新一下)
  8. 了解Linux操作系统
  9. 华为设备MSTP配置命令
  10. 公钥加密数字签名证书的原理总结