# 基础介绍

下载地址 (opens new window)

ES(Elasticsearch)是一个分布式全文搜索引擎

# 操作过程

  • 将被查询的字段的数据全部文本信息进行查分,分成若干个词

    • 例如“中华人民共和国”就会被拆分成三个词,分别是“中华”、“人民”、“共和国”,此过程有专业术语叫做分词。分词的策略不同,分出的效果不一样,不同的分词策略称为分词器。
  • 将分词得到的结果存储起来,对应每条数据的id

    • 例如id为1的数据中名称这一项的值是“中华人民共和国”,那么分词结束后,就会出现“中华”对应id为1,“人民”对应id为1,“共和国”对应id为1
    • 例如id为2的数据中名称这一项的值是“人民代表大会“,那么分词结束后,就会出现“人民”对应id为2,“代表”对应id为2,“大会”对应id为2
    • 此时就会出现如下对应结果,按照上述形式可以对所有文档进行分词。需要注意分词的过程不是仅对一个字段进行,而是对每一个参与查询的字段都执行,最终结果汇总到一个表格中
  • 当进行查询时,如果输入“人民”作为查询条件,可以通过上述表格数据进行比对,得到id值1,2,然后根据id值就可以得到查询的结果数据了

分词得到的结果

# 倒排索引

全文搜索中的分词结果关键字查询后得到的并不是整条的数据,而是数据的id,要想获得具体数据还要再次查询,因此这里为这种分词结果关键字起了一个全新的名称,叫做倒排索引

# 启动服务器

双击elasticsearch.bat文件即可启动ES服务器,默认服务端口9200。通过浏览器访问 http://localhost:9200 看到如下信息视为ES服务器正常启动

{
  "name" : "CZBK-**********",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "j137DSswTPG8U4Yb-0T1Mg",
  "version" : {
    "number" : "7.16.2",
    "build_flavor" : "default",
    "build_type" : "zip",
    "build_hash" : "2b937c44140b6559905130a8650c64dbd0879cfb",
    "build_date" : "2021-12-18T19:42:46.604893745Z",
    "build_snapshot" : false,
    "lucene_version" : "8.10.1",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

# 基本操作

ES中保存有我们要查询的数据,只不过格式和数据库存储数据格式不同而已。在ES中我们要先创建倒排索引,这个索引的功能又点类似于数据库的表,然后将数据添加到倒排索引中,添加的数据称为文档。所以要进行ES的操作要先创建索引,再添加文档,这样才能进行后续的查询操作。

要操作ES可以通过Rest风格的请求来进行,也就是说发送一个请求就可以执行一个操作。比如新建索引,删除索引这些操作都可以使用发送请求的形式来进行。

# 创建索引

// PUT请求(books是索引名称)   http://localhost:9200/books

//发送请求后,看到如下信息即索引创建成功
{
    "acknowledged": true,
    "shards_acknowledged": true,
    "index": "books"
}

重复创建已经存在的索引会出现错误信息,reason属性中描述错误原因

"reason": "index [books/VgC_XMVAQmedaiBNSgO2-w] already exists"

# 查询索引

// GET请求 http://localhost:9200/books

// 查询索引得到索引相关信息,如下
{
    "book": {
        "aliases": {},
        "mappings": {},
        "settings": {
            "index": {
                "routing": {
                    "allocation": {
                        "include": {
                            "_tier_preference": "data_content"
                        }
                    }
                },
                "number_of_shards": "1",
                "provided_name": "books",
                "creation_date": "1645768584849",
                "number_of_replicas": "1",
                "uuid": "VgC_XMVAQmedaiBNSgO2-w",
                "version": {
                    "created": "7160299"
                }
            }
        }
    }
}

如果查询了不存在的索引,会返回错误信息

reason": "no such index [book]"

# 删除索引

// DELETE请求	http://localhost:9200/books

//删除所有后,给出删除结果
{
    "acknowledged": true
}

如果重复删除,会给出错误信息,同样在reason属性中描述具体的错误原因

"reason": "no such index [books]",

# 创建索引并指定分词器

可以在创建索引时添加请求参数,设置分词器。目前较为流行的是IK分词器 IK分词器下载地址 (opens new window)

分词器下载后解压到ES安装目录的plugins目录中即可,安装分词器后需要重新启动ES服务器

// PUT请求 http://localhost:9200/books

// 请求参数如下(注意是json格式的参数)
{
    "mappings":{							#定义mappings属性内容		
        "properties":{						#定义索引中包含的属性设置
            "id":{							#设置索引中包含id属性
                "type":"keyword"			#当前属性可以被直接搜索
            },
            "name":{						#设置索引中包含name属性
                "type":"text",              #当前属性是文本信息,参与分词  
                "analyzer":"ik_max_word",   #使用IK分词器进行分词             
                "copy_to":"all"				#分词结果拷贝到all属性中
            },
            "type":{
                "type":"keyword"
            },
            "description":{
                "type":"text",	                
                "analyzer":"ik_max_word",                
                "copy_to":"all"
            },
            "all":{	     #定义属性,用来描述多个字段的分词结果集合,当前属性可以参与查询
                "type":"text",	                
                "analyzer":"ik_max_word"
            }
        }
    }
}

# 添加文档,有三种方式

POST请求 http://localhost:9200/books/_doc       #使用系统生成id
POST请求 http://localhost:9200/books/_create/1  #使用指定id
POST请求 http://localhost:9200/books/_doc/1     #使用指定id,不存在创建,存在更新(版本递增)

//文档通过请求参数传递,数据格式json
{
    "name":"springboot",
    "type":"springboot",
    "description":"springboot"
}  

# 查询文档

GET请求	http://localhost:9200/books/_doc/1		 #查询单个文档 		
GET请求	http://localhost:9200/books/_search		 #查询全部文档

# 条件查询

GET请求	http://localhost:9200/books/_search?q=name:springboot	# q=查询属性名:查询属性值

# 删除文档

DELETE请求	http://localhost:9200/books/_doc/1

# 修改文档(全量更新)

PUT请求	http://localhost:9200/books/_doc/1

//文档通过请求参数传递,数据格式json
{
    "name":"springboot",
    "type":"springboot",
    "description":"springboot"
}

# 修改文档(部分更新)

POST请求	http://localhost:9200/books/_update/1

//文档通过请求参数传递,数据格式json
{			
    "doc":{						
        "name":"springboot"		
    }
}

# springboot整合

# 早期的操作方式

  • 导入springboot整合ES的starter坐标
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
  • 进行基础配置
spring:
  elasticsearch:
    rest:
      uris: http://localhost:9200
  • 使用springboot整合ES的专用客户端接口ElasticsearchRestTemplate来进行操作
@SpringBootTest
class Springboot18EsApplicationTests {
    @Autowired
    private ElasticsearchRestTemplate template;
}

# 高级别的操作方式

  • 导入springboot整合ES高级别客户端的坐标,此种形式目前没有对应的starter
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>
  • 使用编程的形式设置连接的ES服务器,并获取客户端对象
@SpringBootTest
class Springboot18EsApplicationTests {
	//在测试类中每个操作运行前运行的方法
    @BeforeEach		
    void setUp() {
        HttpHost host = HttpHost.create("http://localhost:9200");
        RestClientBuilder builder = RestClient.builder(host);
        client = new RestHighLevelClient(builder);
    }

    //在测试类中每个操作运行后运行的方法
    @AfterEach		
    void tearDown() throws IOException {
        client.close();
    }

    private RestHighLevelClient client;

    @Test
    void testCreateIndex() throws IOException {
        CreateIndexRequest request = new CreateIndexRequest("books");
        client.indices().create(request, RequestOptions.DEFAULT);
    }
}
  • 创建索引(IK分词器)
@Test
void testCreateIndexByIK() throws IOException {
    CreateIndexRequest request = new CreateIndexRequest("books");
    String json = "{\n" +
            "    \"mappings\":{\n" +
            "        \"properties\":{\n" +
            "            \"id\":{\n" +
            "                \"type\":\"keyword\"\n" +
            "            },\n" +
            "            \"name\":{\n" +
            "                \"type\":\"text\",\n" +
            "                \"analyzer\":\"ik_max_word\",\n" +
            "                \"copy_to\":\"all\"\n" +
            "            },\n" +
            "            \"type\":{\n" +
            "                \"type\":\"keyword\"\n" +
            "            },\n" +
            "            \"description\":{\n" +
            "                \"type\":\"text\",\n" +
            "                \"analyzer\":\"ik_max_word\",\n" +
            "                \"copy_to\":\"all\"\n" +
            "            },\n" +
            "            \"all\":{\n" +
            "                \"type\":\"text\",\n" +
            "                \"analyzer\":\"ik_max_word\"\n" +
            "            }\n" +
            "        }\n" +
            "    }\n" +
            "}";
    //IK分词器是通过请求参数的形式进行设置的,设置请求参数使用request对象中的source方法进行设置
    request.source(json, XContentType.JSON);
    client.indices().create(request, RequestOptions.DEFAULT);
}
  • 添加文档:请求对象是IndexRequest,与创建索引使用的请求对象不同
@Test
//批量添加文档
void testCreateDocAll() throws IOException {
    List<Book> bookList = bookDao.selectList(null);
    BulkRequest bulk = new BulkRequest();
    for (Book book : bookList) {
        IndexRequest request = new IndexRequest("books").id(book.getId().toString());
        String json = JSON.toJSONString(book);
        request.source(json,XContentType.JSON);
        bulk.add(request);
    }
    client.bulk(bulk,RequestOptions.DEFAULT);
}
  • 批量添加文档:先创建一个BulkRequest的对象,可以将该对象理解为是一个保存request对象的容器,将所有的请求都初始化好后,添加到BulkRequest对象中,再使用BulkRequest对象的bulk方法,一次性执行完毕
@Test
//批量添加文档
void testCreateDocAll() throws IOException {
    List<Book> bookList = bookDao.selectList(null);
    BulkRequest bulk = new BulkRequest();
    for (Book book : bookList) {
        IndexRequest request = new IndexRequest("books").id(book.getId().toString());
        String json = JSON.toJSONString(book);
        request.source(json,XContentType.JSON);
        bulk.add(request);
    }
    client.bulk(bulk,RequestOptions.DEFAULT);
}
  • 按id查询文档:使用的请求对象是GetRequest
@Test
//按id查询
void testGet() throws IOException {
    GetRequest request = new GetRequest("books","1");
    GetResponse response = client.get(request, RequestOptions.DEFAULT);
    String json = response.getSourceAsString();
    System.out.println(json);
}
  • 按条件查询文档:使用的请求对象是SearchRequest,查询时调用SearchRequest对象的termQuery方法,需要给出查询属性名,此处支持使用合并字段,也就是前面定义索引属性时添加的all属性
@Test
//按条件查询
void testSearch() throws IOException {
    SearchRequest request = new SearchRequest("books");

    SearchSourceBuilder builder = new SearchSourceBuilder();
    builder.query(QueryBuilders.termQuery("all","spring"));
    request.source(builder);

    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    SearchHits hits = response.getHits();
    for (SearchHit hit : hits) {
        String source = hit.getSourceAsString();
        //System.out.println(source);
        Book book = JSON.parseObject(source, Book.class);
        System.out.println(book);
    }
}