spring boot項目16:ElasticSearch-基礎使用


JAVA 8

Spring Boot 2.5.3

Elasticsearch-7.14.0(Windows)

---

 

授人以漁:

1、Spring Boot Reference Documentation

This document is also available as Multi-page HTML, Single page HTML and PDF.

有PDF版本哦,下載下來!

2、Spring Data Elasticsearch

無PDF,網頁上可以搜索。

3、Elastic官網

 

目錄

0、序章

1、ES安裝及初步使用

配置文件及數據存儲

調用RESTful接口簡單使用ES

2、S.B.中的ES

3、S.B.中使用ES

3.1、檢查elasticsearchRestHighLevelClient Bean並檢查

3.2、生成ElasticsearchRestTemplate Bean並檢查

3.3、使用Spring Data Elasticsearch Repositories存取數據

4、分詞器

參考文檔

 

0、序章

Elasticsearch是一個 開源、分布式、支持RESTful訪問的搜索和分析引擎

ES是一個基於Lucene的搜索服務器,用Java語言開發,並作為Apache許可條款下的開放源碼發布,是一種流行的企業級搜索引擎。

最大的特點是——搜索速度很快,近實時(NRT);它能很方便的使大量數據(PB級)具有搜索、分析和探索的能力。

ES的實現原理主要分為以下幾個步驟,1)首先用戶將數據提交到ES數據庫中,2)再通過分詞控制器去將對應的語句分詞,將其權重和分詞結果一並存入數據,3)當用戶搜索數據時候,再根據權重將結果排名,打分,4)再將返回結果呈現給用戶。

ES是與名為Logstash的數據收集和日志解析引擎以及名為Kibana的分析和可視化平台一起開發的。這三個產品被設計成一個集成解決方案,稱為“Elastic Stack”(以前稱為“ELK stack”)。

Shay Banon在2004年創造了Elasticsearch的前身,稱為Compass。Shay Banon在2010年2月發布了ES的第一個版本。

2015年3月,Elasticsearch公司更名為Elastic,公司於2018年10月5日在紐約證券交易所掛牌上市。

 

關鍵功能:大數據、近實時搜索

 

ES中的關鍵概念:

Cluster 集群,由多個節點組成
Node 節點
Index 索引,相當於數據庫中的 庫
Type 類型,相當於數據庫中的 表;一個索引下有1~N個類型
Document 文檔,記錄,相當於數據庫中的 行,可用一個JSON對象格式表示
Field 文檔中的一個字段,相當於數據庫中的 行中的 列的數據
Shards

切片,索引中的數據切片,以便存儲數據量巨大的索引。

也稱為Primary Shard。

Replicas

Shard的副本,復制品,避免某個節點宕機導致數據丟失。

也稱為Replica Shard。

recovery 數據恢復,節點加入、退出集群,節點重啟后執行數據恢復
river ES的一個數據源,插件,不同插件可以將不同的數據添加到ES數據庫中。
gateway 索引快照的存儲方式,支持多種類型的gateway,有本地文件系統(默認),分布式文件系統,Hadoop的HDFS 和 amazon的s3雲存儲服務。
discovery.zen 自動發現節點機制
Transport 內部節點或集群與客戶端的交互方式

參考:百度百科-elasticsearchElasticsearch核心概念

 

elastic:n.  松緊帶; 橡皮筋; adj.  有彈性的; 靈活的;

 

1、ES安裝及初步使用

本文介紹Windows 10上的安裝,至於 Ubuntu、CentOS、Mac上的安裝,Docker容器化的ES,本文不涉及。

步驟:

打開官網:https://www.elastic.co/

進入下載頁:https://www.elastic.co/start

下載其中Windows版本的Elasticsearch、Kibana——最新的適合自己主機的版本,也可以點擊頁面的 Visit our downloads page 鏈接進去下載:

官方文檔:Read our documentation 鏈接進入:

ES的官文

Kibana的官文

 

下載得到:

elasticsearch-7.14.0-windows-x86_64.zip

kibana-7.14.0-windows-x86_64.zip

解壓,得到文件夾。

進入各自目錄的bin目錄,執行 elasticsearch.bat 啟動ES、執行 kibana.bat 啟動 Kibana。

 

ES默認端口:9200(還有一個 9300端口,集群使用)

Kibana默認端口:5601

 

訪問ES:

http://localhost:9200/

得到:

{
  "name" : "---主機名---",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "mACMicetS3-YnNTGbiDLXA",
  "version" : {
    "number" : "7.14.0",
    "build_flavor" : "default",
    "build_type" : "zip",
    "build_hash" : "dd5a0a2acaa2045ff9624f3729fc8a6f40835aa1",
    "build_date" : "2021-07-29T20:49:32.864135063Z",
    "build_snapshot" : false,
    "lucene_version" : "8.9.0",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

其它一些鏈接:

/_cat

可以查看到不少有用的信息,比如,ES中有多少Index(復數:indices,非規則變化)。下圖現實了 自己的不久前安裝的ES中已經有不少Index了,其中,不少是Kibana的。

/_search

返回一大推東西,暫不清楚是干啥的。Kibana中發現。

...還有更多待探索...

 

訪問Kibana:

http://localhost:5601/

得到Kibana的主頁。

注,作者還不太熟悉Kibana的使用,僅展示幾張簡單的使用圖。

Kibina還可以實現 添加數據 的功能(待探索):數據來源很多,MySQL、kafka、RabbitMQ、MongoDB……好多,好像很簡單的樣子。為什么沒找到HADOOP/HDFS?

 

配置文件及數據存儲

ES目錄結構:

權限不配置,默認無需用戶、密碼登錄:

/data/nodes 節點目錄,單機,只有一個節點0(目錄),其下結構:indices存儲索引(數據)

注,默認情況下,ES是無需用戶密碼登錄的

 

Kibana目錄結構:

在其配置config中,和ES有關的 部分配置 整理如下:

# 部分配置
#elasticsearch.hosts: ["http://localhost:9200"]
#kibana.index: ".kibana"
#elasticsearch.username: "kibana_system"
#elasticsearch.password: "pass"

#elasticsearch.pingTimeout: 1500
#elasticsearch.requestTimeout: 30000
#elasticsearch.shardTimeout: 30000
...

 

調用RESTful接口簡單使用ES

接下來,可以在 Kibana的 Management->Dev Tools 下簡單使用 ES(參考文檔6)了——增刪改查數據

注,也可以使用Postman、curl命令等工具

 

1)添加

添加一個 Document:POST請求,/object/animal,其中 的 object為 Index,animal為 Type,有一個默認的Type為 _doc。

添加時,沒有Index,會自動建立Index等數據信息。

添加語句及結果
POST /object/animal
{
  "name": "cat",
  "number": 100
}

結果:返回了index、type、id,,id是每個字段必須的
{
  "_index" : "object",
  "_type" : "animal",
  "_id" : "5gKP6HsBU7Hs6OzNeM4G",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 0,
  "_primary_term" : 1
}

http://localhost:9200/_cat/indices 可以看到新增了 Index,名為 object:

yellow open object                          lEBGDTG-TGOuNCv0_j2Fvw 1 1  1    0     4kb     4kb

還可以訪問 /_cat/indices?v ,可以看到更多信息。

 

2)查找

查找1:使用id

GET /object/animal/5gKP6HsBU7Hs6OzNeM4G

返回:一個完整的Document,包含 元數據

{
  "_index" : "object",
  "_type" : "animal",
  "_id" : "5gKP6HsBU7Hs6OzNeM4G",
  "_version" : 1,
  "_seq_no" : 0,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "name" : "cat",
    "number" : 100
  }
}

查找2:使用/id/_source,,只是Document的數據了

GET /object/animal/5gKP6HsBU7Hs6OzNeM4G/_source

{
  "name" : "cat",
  "number" : 100
}

 

3)更改

POST請求,使用 _update。

根據ID更新一條文檔
POST /object/animal/5gKP6HsBU7Hs6OzNeM4G/_update
{
  "doc": {
    "name": "cat2"
  }
}

響應:
{
  "_index" : "object",
  "_type" : "animal",
  "_id" : "5gKP6HsBU7Hs6OzNeM4G",
  "_version" : 2,
  "result" : "updated",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 1,
  "_primary_term" : 1
}

檢查是否更新成功:成功
{
  "name" : "cat2",
  "number" : 100
}

 

4)刪除一條文檔

DELETE請求,下面演示根據 ID刪除。

刪除及結果
DELETE /object/animal/5gKP6HsBU7Hs6OzNeM4G

響應:
{
  "_index" : "object",
  "_type" : "animal",
  "_id" : "5gKP6HsBU7Hs6OzNeM4G",
  "_version" : 3,
  "result" : "deleted",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 2,
  "_primary_term" : 1
}

檢查:刪除后,數據沒有了
{
  "_index" : "object",
  "_type" : "animal",
  "_id" : "5gKP6HsBU7Hs6OzNeM4G",
  "found" : false
}

刪除了 索引object 下唯一的Document,但是,索引object 沒有被刪除。

 

5)根據條件查找

注,執行前,先添加3條數據。

 

GET /object/animal/_search

沒有參數,搜索到所有。來自博客園

 

POST請求,根據name搜索dog:

根據name搜索dog
POST /object/animal/_search
{
  "query": {
    "match": {
      "name": "dog"
    }
  }
}

響應:
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.9808291,
    "hits" : [
      {
        "_index" : "object",
        "_type" : "animal",
        "_id" : "6AKe6HsBU7Hs6OzN5M6U",
        "_score" : 0.9808291,
        "_source" : {
          "name" : "dog",
          "number" : 90
        }
      }
    ]
  }
}

但使用參考文檔6中的搜索條件 from、size 時失敗了:版本問題造成的嗎?TODO

失敗結果
POST /object/animal/_search
{
  "query": {
    "match": {
      "name": "dog"
    },
    "from":1,
    "size":4
  }
}

響應:
{
  "error" : {
    "root_cause" : [
      {
        "type" : "parsing_exception",
        "reason" : "[match] malformed query, expected [END_OBJECT] but found [FIELD_NAME]",
        "line" : 6,
        "col" : 5
      }
    ],
    "type" : "parsing_exception",
    "reason" : "[match] malformed query, expected [END_OBJECT] but found [FIELD_NAME]",
    "line" : 6,
    "col" : 5
  },
  "status" : 400
}

 

6)刪除類型、刪除索引

刪除類型:失敗

DELETE /object/animal

 

刪除索引:成功

DELETE /object

返回:

{
  "acknowledged" : true
}

 

奇怪的是,刪除了 索引object,但是, http://localhost:9200/_cat/indices/ 中多了一個 索引animal——類型animal升級了嗎?:

green  open .geoip_databases                un0e_HOORSWwQ-IrKfJO0A 1 0 42   72  77.6mb  77.6mb
yellow open pp                              c-0HoI9ISZqeNUtT_7eTYw 1 1  2    1   8.9kb   8.9kb
green  open .kibana-event-log-7.14.0-000001 gAPi_xD7SA-kwjiE0YbOLA 1 0  5    0  27.2kb  27.2kb
yellow open news                            dQET35lZQ4m-eFrFKkwqBA 1 1  8    0  85.4kb  85.4kb
green  open .kibana_7.14.0_001              cvROU6mcTxaWuzRWCv9C1Q 1 0 90   57   4.5mb   4.5mb
green  open .apm-custom-link                ZTe43CV3Sz-5-Z70NY2MjQ 1 0  0    0    208b    208b
green  open .apm-agent-configuration        a_XNtGigTw6ROuF2r-GHBQ 1 0  0    0    208b    208b
green  open .kibana_task_manager_7.14.0_001 Kf9q48NZS1Gwl1NuDSnJyw 1 0 14 3939 668.1kb 668.1kb
yellow open animal                          gRdOhMXbQ5OszQRxS51Jkg 1 1  0    0    208b    208b
green  open .tasks                          Wg-94Ql8R5ux1gmQ-fXW6Q 1 0  4    0  27.3kb  27.3kb

GET /animal:存在數據,但這個有什么用呢?

訪問結果
{
  "animal" : {
    "aliases" : { },
    "mappings" : { },
    "settings" : {
      "index" : {
        "routing" : {
          "allocation" : {
            "include" : {
              "_tier_preference" : "data_content"
            }
          }
        },
        "number_of_shards" : "1",
        "provided_name" : "animal",
        "creation_date" : "1631694292667",
        "number_of_replicas" : "1",
        "uuid" : "gRdOhMXbQ5OszQRxS51Jkg",
        "version" : {
          "created" : "7140099"
        }
      }
    }
  }
}

 

執行:DELETE /animal

刪除成功。

# 響應
{
  "acknowledged" : true
}

 

以上都是在Kibana中操作,這個工具真是好啊!還可以把這么多執行記錄保存下來,還有提示信息!來自博客園

應該還有更加強大、便捷的功能 待解鎖。

 

2、S.B.中的ES

S.B.為ES提供了基本的自動配置功能。

S.B.提供了3種客戶端(clients):

  1. 官方的Java底層REST客戶端(Transport Client)
  2. 官方的Java高級REST客戶端(High Level REST Client)
  3. ReactiveElasticsearchClient

雖然有這3種客戶端,應用程序一般使用 Spring Data Elasticsearch 中更高級的抽象——Elasticsearch Operations 和 Elasticsearch Repositories。

 

添加依賴包:spring-boot-starter-data-elasticsearch


	org.springframework.boot
	spring-boot-starter-data-elasticsearch

其結構展示如下:

添加依賴包后,應用程序會生產一個RestHighLevelClient Bean到Spring容器。

如果想要任意數量的、更多高級定制的Bean,去實現 函數式接口RestClientBuilderCustomizer。

如果想要完全控制 注冊過程,需要實現一個 RestClientBuilder Bean。

如果想要訪問 底層REST客戶端,可以使用自動注冊的Bean的getLowLevelClient()獲取。

上面的客戶端對應的配置:

# 默認本機9200端口
spring.elasticsearch.rest.uris=https://search.example.com:9200
spring.elasticsearch.rest.read-timeout=10s
spring.elasticsearch.rest.username=user
spring.elasticsearch.rest.password=secret

 

如果想要使用 Reactive REST clients,還需要引入 spring-boot-starter-webflux

ReactiveElasticsearchClient 的配置和前面不同,部分展示如下:spring.data開頭的

spring.data.elasticsearch.client.reactive.endpoints=search.example.com:9200
spring.data.elasticsearch.client.reactive.use-ssl=true
spring.data.elasticsearch.client.reactive.socket-timeout=10s
spring.data.elasticsearch.client.reactive.username=user
spring.data.elasticsearch.client.reactive.password=secret

注,本文不涉及Reactive REST clients更多使用介紹

 

使用Spring Data

在RestHighLevelClient Bean已經注冊的情況下,

@Component
public class MyBean {

  private final ElasticsearchRestTemplate template;
  
  public MyBean(ElasticsearchRestTemplate template) {
    this.template = template;
  }
  // ...
}

 

下面的方式也可以:

@Configuration
public class AppConfig {

    // 注意,方法名中么有Rest!!
	@Bean
	public ElasticsearchRestTemplate elasticsearchTemplate(RestHighLevelClient client) {
		return new ElasticsearchRestTemplate(client);
	}
	
}

 

疑問:兩種方式有什么區別呢?

 

使用Spring Data Elasticsearch Repositories

類似Spring Data JPA,不同的是,定義數據(索引等)使用的是 @Document(org.springframework.data.elasticsearch.annotations.Document) 加上 org.springframework.data.elasticsearch.annotations.Setting 注解(設置、配置等)來自博客園

還要使用 ElasticsearchRepository接口(org.springframework.data.elasticsearch.repository.ElasticsearchRepository)。

這種方式可以通過下面的配置禁止(默認是 使能的——true):

spring.data.elasticsearch.repositories.enabled=false

 

ES已啟動、應用程序的依賴包和配置已添加好,啟動應用程序,檢查Spring容器中和ES相關的Bean

果然有一個 elasticsearchRestHighLevelClient,檢查發現 其類型為 org.elasticsearch.client.RestHighLevelClient——類名前面沒有 Elasticsearch

name=org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientConfigurations$RestClientBuilderConfiguration
name=defaultRestClientBuilderCustomizer
name=elasticsearchRestClientBuilder
name=org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientConfigurations$RestHighLevelClientConfiguration
name=elasticsearchRestHighLevelClient
name=org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration
name=spring.elasticsearch.rest-org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientProperties

 

ES默認配置下,啟動應用程序時有一條WARN日志:安全驗證未開啟,TODO

org.elasticsearch.client.RestClient      : request [GET http://localhost:9200/] returned 1 warnings: 
[299 Elasticsearch-7.14.0-dd5a0a2acaa2045ff9624f3729fc8a6f40835aa1 "Elasticsearch built-in security 
features are not enabled. Without authentication, your cluster could be accessible to anyone. 
See https://www.elastic.co/guide/en/elasticsearch/reference/7.14/security-minimal-setup.html 
to enable security."]

 

小結:

本節對S.B.中的ES使用做了一個簡單梳理,接下來,就是要怎么使用的問題了。

 

3、S.B.中使用ES

3.1、檢查elasticsearchRestHighLevelClient Bean並檢查

檢查源碼:

EsController.java
@RestController
@RequestMapping(value="/es")
@Slf4j
public class EsController {

	static Consumer cs = System.out::println;

	@Autowired
	private RestHighLevelClient client;
	
	@Autowired
	private ElasticsearchRestClientProperties properties;
	
	/**
	 * 檢查RestHighLevelClient Bean
	 * @author ben
	 * @date 2021-09-16 10:28:59 CST
	 * @return
	 */
	@GetMapping(value="/check/client")
	public Boolean checkClient() {
		cs.accept("client=" + client);
		RestClient llrc = client.getLowLevelClient();
		if (Objects.nonNull(llrc)) {
			cs.accept("LowLevelClient=" + llrc);
			cs.accept("isRunning=" + llrc.isRunning());
			
			List list = llrc.getNodes();
			cs.accept("list.size=" + list.size());
			Node node = list.get(0);
			cs.accept("node[0]=" + node);
			cs.accept("n=" + node.getName());
			cs.accept("v=" + node.getVersion());
			cs.accept("h=" + node.getHost());
			cs.accept("rs=" + node.getRoles());
			cs.accept("as=" + node.getAttributes());
		}
		cs.accept("cluster=" + client.cluster());
		cs.accept("indices=" + client.indices());
		cs.accept("ingest=" + client.ingest());
		cs.accept("machineLearning=" + client.machineLearning());
		return true;
	}

	/**
	 * 檢查ElasticsearchRestClientProperties Bean
	 * @author ben
	 * @date 2021-09-16 10:28:59 CST
	 * @return
	 */
	@GetMapping(value="/check/client/properties")
	public Boolean checkClientProperties() {
		cs.accept("---properties=" + properties);
		cs.accept("uris=" + properties.getUris());
		cs.accept("username=" + properties.getUsername());
		cs.accept("password=" + properties.getPassword());
		cs.accept("connectionTimeout=" + properties.getConnectionTimeout().getSeconds());
		cs.accept("readTimeout=" + properties.getReadTimeout().getSeconds());
		
		Sniffer sn = properties.getSniffer();
		if (Objects.nonNull(sn)) {
			cs.accept("---Sniffer=" + sn);
			cs.accept("DelayAfterFailure=" + sn.getDelayAfterFailure().getSeconds());
			cs.accept("Interval=" + sn.getInterval().getSeconds());
		} else {
			cs.accept("Sniffer is null");
		}
		return true;
	}
	
}

 

訪問接口:/es/check/client

client=org.elasticsearch.client.RestHighLevelClient@218af644
LowLevelClient=org.elasticsearch.client.RestClient@3fbc4863
isRunning=true
list.size=1
node[0]=[host=http://localhost:9200]
n=null
v=null
h=http://localhost:9200
rs=null
as=null
cluster=org.elasticsearch.client.ClusterClient@61284417
indices=org.elasticsearch.client.IndicesClient@260f8abb
ingest=org.elasticsearch.client.IngestClient@504160e8
machineLearning=org.elasticsearch.client.MachineLearningClient@d63c5d3

 

訪問接口:/es/check/client/properties

---properties=org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientProperties@29b4560c
uris=[http://localhost:9200]
username=null
password=null
connectionTimeout=1
readTimeout=15
---Sniffer=org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientProperties$Sniffer@2773bd4a
DelayAfterFailure=60
Interval=300

在S.B.中,提供了下面的配置,但沒有找到Sniffer的配置:

 

3.2、生成ElasticsearchRestTemplate Bean並檢查

默認就生產了一個 elasticsearchTemplate Bean——注意沒有Rest。

檢查代碼:

	// 文件EsController.java
	@Autowired
	private ElasticsearchRestTemplate esTemplate;
    
    @GetMapping(value="/check/template")
	public Boolean checkEsTemplate() {
		cs.accept("esTemplate=" + esTemplate);
		cs.accept("esTemplate=" + esTemplate.getElasticsearchConverter());
		cs.accept("esTemplate=" + esTemplate.getRefreshPolicy());
		cs.accept("esTemplate=" + esTemplate.getRequestFactory());
		
		return true;
	}

測試結果:

esTemplate=org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate@12437e95
esTemplate=org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter@7d7d58c8
esTemplate=null
esTemplate=org.springframework.data.elasticsearch.core.RequestFactory@2ecd00d5

其中的getRefreshPolicy() 返回 null。

org.springframework.data.elasticsearch.core.RefreshPolicy默認有 3個:

public enum RefreshPolicy {
	NONE, IMMEDIATE, WAIT_UNTIL;
}

默認為null,此時可以注冊一個自己的ElasticsearchRestTemplate Bean來替代默認的——並做更多的設置:來自博客園

疑問:設置了有什么好處呢?TODO

設置后檢查:

 

ElasticsearchRestTemplate 是組合了 RestHighLevelClient client 實現的,是一種更高級的抽象。

而 ElasticsearchRestTemplate  又被 更高級的抽象 Spring Data Elasticsearch 等使用。

其自身的public函數展示:

其繼承的抽象類AbstractElasticsearchTemplate 中有更多的public函數——比如前面圖片中展示的幾個 set函數 就來自抽象類。

 

3.3、使用Spring Data Elasticsearch Repositories存取數據

S.B.通過了更高級的抽象來操作ES,本文介紹使用ElasticsearchRepository來管理數據。

先來看看類型的關系:

package org.springframework.data.elasticsearch.repository;
@NoRepositoryBean
public interface ElasticsearchRepository extends PagingAndSortingRepository {
}

package org.springframework.data.repository;
@NoRepositoryBean
public interface PagingAndSortingRepository extends CrudRepository {
}

package org.springframework.data.repository;
@NoRepositoryBean
public interface CrudRepository extends Repository {
}

ElasticsearchRepository的實現類:

本節演示在ES中保存新聞數據,以及根據關鍵詞查詢新聞等功能。

 

使用步驟:

1)定義實體類News

org.springframework.data.elasticsearch.annotations.Document注解,即可。

如要更多配置,還要使用 org.springframework.data.elasticsearch.annotations.Setting注解。

建立實體類,啟動項目,此時,ES中不會建立索引。

@Document(indexName = "news")
@Data
public class News {

	protected News() {
	}
	
	/**
	 * 構造函數
	 * @param title
	 * @param url
	 * @param postTime
	 * @param source
	 * @param contentText
	 * @param contentHtml
	 */
	public News(String title, String url, Date postTime, String source, String contentText, String contentHtml) {
		this.title = title;
		this.url = url;
		this.postTime = postTime;
		this.source = source;
		this.contentText = contentText;
		this.contentHtml = contentHtml;
	}
	
	@Id
	private String id;

	/**
	 * 標題
	 */
	private String title;
	/**
	 * url
	 */
	private String url;
	/**
	 * 發布時間
	 */
	private Date postTime;
	
	/**
	 * 新聞來源
	 */
	private String source;

	/**
	 * 內容:文本
	 */
	private String contentText;
	
	/**
	 * 內容:HTML
	 */
	private String contentHtml;
	
}

2)添加接口NewsDAO

繼承 ElasticsearchRepository接口,並添加一些方法。

此時啟動項目,ES中會建立好索引。

public interface NewsDAO extends ElasticsearchRepository {

	List findByTitle(String title);
	
	// 沒有數據返回
//	List findByTitleContaining(String title);
	// 沒有數據返回
	Streamable findByTitleContaining(String title);
	
	/**
	 * 根據 title、contentText 分頁查找
	 * @author ben
	 * @date 2021-09-01 16:02:10 CST
	 * @param title
	 * @param contentText
	 * @param page
	 * @return
	 */
	Page findByTitleOrContentText(String title, String contentText, Pageable page);
	
	/**
	 * findByTitleContainingOrContentTextContaining
	 * @author ben
	 * @date 2021-09-01 19:26:53 CST
	 * @param title
	 * @param contentText
	 * @param page
	 * @return
	 */
	Page findByTitleContainingOrContentTextContaining(String title, String contentText, Pageable page);
	
}

此時已建立好索引,可以使用NewsDAO Bean操作ES了。

啟動日志-建立索引部分:一個HEAD、一個PUT請求。再次啟動項目,因為索引news已經建立,此時只會發送HEAD請求。

org.elasticsearch.client.RestClient      : request [HEAD http://localhost:9200/news?ignore_throttled=false&ignore_unavailable=false&expand_wildcards=open%2Cclosed&allow_no_indices=false] returned 1 
    warnings: [299 Elasticsearch-7.14.0-dd5a0a2acaa2045ff9624f3729fc8a6f40835aa1 "Elasticsearch built-in security features are not enabled. Without authentication, your cluster could be accessible to anyone. See https://www.elastic.co/guide/en/elasticsearch/reference/7.14/security-minimal-setup.html to enable security."]
org.elasticsearch.client.RestClient      : request [PUT http://localhost:9200/news?master_timeout=30s&timeout=30s] returned 1 
    warnings: [299 Elasticsearch-7.14.0-dd5a0a2acaa2045ff9624f3729fc8a6f40835aa1 "Elasticsearch built-in security ...]

/_cat/indices 得到:

yellow open news MuAdniZvTCiURzDKHUEhHA 1 1  0    0   208b   208b

/_cat/indices?v 得到:第一行有標題

health status index                           uuid                   pri rep docs.count docs.deleted store.size pri.store.size
yellow open   news                            MuAdniZvTCiURzDKHUEhHA   1   1          0            0       208b           208b

試着訪問 /news、/news/_search 接口,檢查響應。

 

3)編寫Web接口操作ES

編寫服務層、Controller層等代碼。來自博客園

3.1)添加

接口 /news/add:

	// NewsServiceImpl.java中
    @Autowired
	private NewsDAO newsDao;

	@Override
	public String addNews(AddNewsDTO dto) {
		News news = dto.convertToNews();
		News savedNews = newsDao.save(news);
		log.info("添加新聞:id={}", savedNews.getId());
		return savedNews.getId();
	}

添加成功,返回ID:

4ae38XsBzdSOdMPtHw1i

此時,ES的 http://localhost:9200/news/_search 接口可以看到新增數據了:由於前面的實體類沒有使用 Setting注解,因此,這里的文檔的type為默認的 _doc

添加時,應用程序發送了兩個POST請求:

 

3.2)查詢(1)

	// 文件NewsServiceImpl.java
    
    // 查詢所有
    @Override
	public List getAll() {
		List ret = new ArrayList(20);
		
		Iterable iter = newsDao.findAll();
		iter.forEach(item -> {
			ret.add(item);
		});
		
		return ret;
	}

	// 根據ID查找
	@Override
	public News getById(String id) {
		return newsDao.findById(id).orElse(null);
	}

 

3.3)查詢(2)

測試數據:

{
"id": "4ae38XsBzdSOdMPtHw1i",
"title": "神舟十二號撤離空間組合體 着陸場系統做好回收准備",
"url": "https://www.163.com/news/article/GK3CJ3CI000189FH.html",
"postTime": "2021-09-17 10:08:42",
"source": "網易",
"contentText": "央視網消息(新聞聯播):今天(9月16日),神舟十二號載人飛船與空間站天和核心艙成功實施分離並進行了繞飛及徑向交會試驗。與此同時,着陸場系統各項工作准備就緒,全力迎接航天員平安返回。",
"contentHtml": ""
}

/news/findByTitle:

本以為是 根據標題精確匹配查詢,查詢結果顯示,不會這樣的

輸入完整標題、不完整標題,都可以找到這條記錄。

 

/news/findByTitleContaining:

- 輸入完整標題,查詢發生異常

2021-09-17 11:32:33.137 ERROR 19460 --- [io-30005-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: Cannot constructQuery '*"神舟十二號撤離空間組合體 着陸場系統做好回收准備"*'. Use expression or multiple clauses instead.] with root cause

org.springframework.dao.InvalidDataAccessApiUsageException: Cannot constructQuery '*"神舟十二號撤離空間組合體 着陸場系統做好回收准備"*'. Use expression or multiple clauses instead.

- 只輸入 “神舟十二號”,返回空字符串

- 輸入 “神”,可以查到記錄

其它單個字 也行。

- 執行下面的語句,輸出了所有記錄

http://localhost:30005/news/findByTitleContaining?title=

title后沒有數據。

……

這個要怎么用呢?還需要看看文檔才是,TODO

注,此問題和使用的 分詞器 有關系

 

3.4)查詢(3)

查詢 標題title和內容contentText 中都有文檔。來自博客園

測試關鍵代碼:

分別對 方式1)newsDao.findByTitleOrContentText 和 方式2)newsDao.findByTitleContainingOrContentTextContaining 進行測試。

FindByPageDTO.java
@Data
public class FindByPageDTO {

	/**
	 * 頁數:從0開始
	 */
	private int pageNo;
	
	/**
	 * 每頁記錄數:默認10
	 */
	private int pageSize;
	
	/**
	 * 關鍵詞:標題 或 內容
	 */
	private String keyword;
	
}


NewsDAO.java
	/**
	 * 根據 title、contentText 分頁查找
	 * @author ben
	 * @date 2021-09-01 16:02:10 CST
	 * @param title
	 * @param contentText
	 * @param page
	 * @return
	 */
	Page findByTitleOrContentText(String title, String contentText, Pageable page);
	
	/**
	 * findByTitleContainingOrContentTextContaining
	 * @author ben
	 * @date 2021-09-01 19:26:53 CST
	 * @param title
	 * @param contentText
	 * @param page
	 * @return
	 */
	Page findByTitleContainingOrContentTextContaining(String title, String contentText, Pageable page);


NewsServiceImpl.java
	@Override
	public PageVO findByTitleOrContentText(FindByPageDTO dto) {
		PageVO page = new PageVO();
		page.setPageNo(dto.getPageNo());
		page.setPageSize(dto.getPageSize());
		
//		Pageable pageable = PageRequest.of(dto.getPageNo(), dto.getPageSize());
		// 排序:按照 postTime 倒序返回
		Pageable pageable = PageRequest.of(dto.getPageNo(), dto.getPageSize(), Sort.by(Direction.DESC, "postTime"));
		// 方式1:單個漢字,查詢結果不全
//		Page daoPage = newsDao.findByTitleOrContentText(dto.getKeyword(), dto.getKeyword(), pageable);
		// 方式2:單個漢字,可以查到符合要求的所有
		Page daoPage = newsDao.findByTitleContainingOrContentTextContaining(dto.getKeyword(), dto.getKeyword(), pageable);
		
		log.info("daoPage={}", daoPage);
		page.setTotalPage(daoPage.getTotalPages());
		page.setTotalSize(daoPage.getTotalElements());
		// 內容
		page.setData(daoPage.getContent());
		
		return page;
	}

 

方式1:

"keyword": "" 沒有返回數據
"keyword": "單個漢字" 可以找到記錄
"keyword": "一段文檔中較長的文字" 可以找到記錄
"keyword": "標題" 可以找到記錄

方式2:

"keyword": "" 返回所有數據

"keyword": "單個漢字" 可以找到記錄

"keyword": "一段文檔中較長的文字" 找不到記錄

"keyword": "標題"  找不到記錄

……

更多區別,請大家自行試驗。

我自己還要去好好看看文檔才是——Spring Data Elasticsearch - Reference Documentation。

 

本小結只是試驗了ES的基本用法,實際上還有更多高級的操作可以使用的,比如,聚合查詢,還需繼續探索。

 

4、分詞器

在序章中提到了,要使用 分詞器 對用戶輸入的數據進行處理,然后存庫或搜索使用。

中文等象形文字 和 英文等字符組合型文字 的分詞方法是不同的。比如,英文中分詞使用空格(書寫的時候),但是中文呢?沒有空格,需要根據使用經驗來做分詞。

因此,在ES中,中文需要使用特別的分詞器類做分詞,否則,操作結果會嚴重不符合預期。

參考文檔中的 一些博文講的很好了,本小結就介紹ES中分詞器的基本使用。

 

ES官方文檔:Text analysis

 

4.1、默認分詞器

ES官方文檔 Tokenizer reference 展示了所有的分詞器(好多)。

其中,Standard Analyzer 是默認的。

下圖來自參考文檔,中文介紹了一些默認分詞器的作用:

驗證默認分詞器的分詞效果:英文部分按照 空格分開了,而中文部分則被拆分成一個一個字。

standard分詞器測試
POST http://localhost:9200/_analyze
參數:
{
    "analyzer": "standard",
    "text": "this is a 橫斷山脈"
}
響應:
{
    "tokens": [
        {
            "token": "this",
            "start_offset": 0,
            "end_offset": 4,
            "type": "",
            "position": 0
        },
        {
            "token": "is",
            "start_offset": 5,
            "end_offset": 7,
            "type": "",
            "position": 1
        },
        {
            "token": "a",
            "start_offset": 8,
            "end_offset": 9,
            "type": "",
            "position": 2
        },
        {
            "token": "橫",
            "start_offset": 10,
            "end_offset": 11,
            "type": "",
            "position": 3
        },
        {
            "token": "斷",
            "start_offset": 11,
            "end_offset": 12,
            "type": "",
            "position": 4
        },
        {
            "token": "山",
            "start_offset": 12,
            "end_offset": 13,
            "type": "",
            "position": 5
        },
        {
            "token": "脈",
            "start_offset": 13,
            "end_offset": 14,
            "type": "",
            "position": 6
        }
    ]
}

更多試驗,不同類型的分詞器的分詞效果,請自行試驗。

 

4.2、安裝使用中文分詞器

對於中文分詞器,除了留下的 IK分詞器,還有 smartCN、HanLP 等。

本節介紹 IK分詞器的安裝,很簡單:

elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.14.0/elasticsearch-analysis-ik-7.14.0.zip

唯一要注意的就是:版本號!其中的 v.7.14.0!否則會安裝失敗。

安裝好后,可以看到ES的plugins下多了一個 analysis-ik 目錄,ES的/_cat/plugins接口 可以看到安裝成功了。

 

IK分詞器有兩種分詞方法:

ik_smart: 會做最粗粒度的拆分

ik_max_word: 會將文本做最細粒度的拆分

 

下面分別使用它們來做試驗:

下面試驗中,中文沒有被拆分為一個一個字了,可是,英文跑哪里去了呢——兩種分詞方法都把英文搞丟了?中英文混合分詞怎么弄呢?TODO

IK初體驗
http://localhost:9200/_analyze
試驗1:
參數:
{
    "analyzer": "ik_smart",
    "text": "this is a 橫斷山脈"
}
響應:
{
    "tokens": [
        {
            "token": "橫斷",
            "start_offset": 10,
            "end_offset": 12,
            "type": "CN_WORD",
            "position": 0
        },
        {
            "token": "山脈",
            "start_offset": 12,
            "end_offset": 14,
            "type": "CN_WORD",
            "position": 1
        }
    ]
}

試驗2:
參數:
{
    "analyzer": "ik_max_word",
    "text": "this is a 橫斷山脈"
}
響應:
{
    "tokens": [
        {
            "token": "橫斷山",
            "start_offset": 10,
            "end_offset": 13,
            "type": "CN_WORD",
            "position": 0
        },
        {
            "token": "橫斷",
            "start_offset": 10,
            "end_offset": 12,
            "type": "CN_WORD",
            "position": 1
        },
        {
            "token": "山脈",
            "start_offset": 12,
            "end_offset": 14,
            "type": "CN_WORD",
            "position": 2
        }
    ]
}

 

4.3、安裝使用拼音(Pinyin)分詞器

安裝方式同上面的中文分詞器:

./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-pinyin/releases/download/v7.14.0/elasticsearch-analysis-pinyin-7.14.0.zip

同樣也要注意版本號!否則失敗。

同樣在ES的plugins下會多一個 analysis-pinyin目錄,ES的/_cat/plugins接口 可以看到安裝成功了。

拼音分詞器的Github介紹:

The plugin includes analyzer: pinyin , tokenizer: pinyin and token-filter: pinyin.

只有一個分詞方法:pinyin

 

拼音分詞器試驗:

試驗結果
POST http://localhost:9200/_analyze
參數:
{
    "analyzer": "pinyin",
    "text": "this is a 橫斷山脈"
}
響應:
{
    "tokens": [
        {
            "token": "t",
            "start_offset": 0,
            "end_offset": 0,
            "type": "word",
            "position": 0
        },
        {
            "token": "thisisahdsm",
            "start_offset": 0,
            "end_offset": 0,
            "type": "word",
            "position": 0
        },
        {
            "token": "h",
            "start_offset": 0,
            "end_offset": 0,
            "type": "word",
            "position": 1
        },
        {
            "token": "i",
            "start_offset": 0,
            "end_offset": 0,
            "type": "word",
            "position": 2
        },
        {
            "token": "si",
            "start_offset": 0,
            "end_offset": 0,
            "type": "word",
            "position": 3
        },
        {
            "token": "sa",
            "start_offset": 0,
            "end_offset": 0,
            "type": "word",
            "position": 4
        },
        {
            "token": "heng",
            "start_offset": 0,
            "end_offset": 0,
            "type": "word",
            "position": 5
        },
        {
            "token": "duan",
            "start_offset": 0,
            "end_offset": 0,
            "type": "word",
            "position": 6
        },
        {
            "token": "shan",
            "start_offset": 0,
            "end_offset": 0,
            "type": "word",
            "position": 7
        },
        {
            "token": "mai",
            "start_offset": 0,
            "end_offset": 0,
            "type": "word",
            "position": 8
        }
    ]
}

分的好細啊!中文也被拆分為了拼音!

 

4.4、給索引配置中文分詞器后驗證前面的查詢結果

前面建立了索引news,沒有指定的情況下,肯定是用的默認standard分詞器了。

本節介紹更改其分詞器為 ik_smart(其實,還可以更進一步設置分詞器,本文不介紹——還不熟悉啊)。

 

開始操作:

操作去的 GET /news/_settings 結果:

結果1
{
  "news" : {
    "settings" : {
      "index" : {
        "routing" : {
          "allocation" : {
            "include" : {
              "_tier_preference" : "data_content"
            }
          }
        },
        "refresh_interval" : "1s",
        "number_of_shards" : "1",
        "provided_name" : "news",
        "creation_date" : "1631846293902",
        "store" : {
          "type" : "fs"
        },
        "number_of_replicas" : "1",
        "uuid" : "MuAdniZvTCiURzDKHUEhHA",
        "version" : {
          "created" : "7140099"
        }
      }
    }
  }
}

更改分詞器為ik_smart(最簡配置):更改失敗,刪除索引后,重新建立並設置分詞器為ik_smart

新建索引設置分詞器
注,執行時,索引已經存在。

PUT /news
{
    "settings": {
		"analysis": {
			"analyzer": {
				"default": {
					"type":"ik_smart"
				}
			}
		}
	}
}
結果:
{
  "error" : {
    "root_cause" : [
      {
        "type" : "resource_already_exists_exception",
        "reason" : "index [news/MuAdniZvTCiURzDKHUEhHA] already exists",
        "index_uuid" : "MuAdniZvTCiURzDKHUEhHA",
        "index" : "news"
      }
    ],
    "type" : "resource_already_exists_exception",
    "reason" : "index [news/MuAdniZvTCiURzDKHUEhHA] already exists",
    "index_uuid" : "MuAdniZvTCiURzDKHUEhHA",
    "index" : "news"
  },
  "status" : 400
}

錯誤原因:
索引已經存在。

刪除索引,重新執行:
PUT /news
{
    "settings": {
		"analysis": {
			"analyzer": {
				"default": {
					"type":"ik_smart"
				}
			}
		}
	}
}


檢查新建索引配置:
GET /news/_settings
響應:
{
  "news" : {
    "settings" : {
      "index" : {
        "routing" : {
          "allocation" : {
            "include" : {
              "_tier_preference" : "data_content"
            }
          }
        },
        "number_of_shards" : "1",
        "provided_name" : "news",
        "creation_date" : "1631860976432",
        "analysis" : {
          "analyzer" : {
            "default" : {
              "type" : "ik_smart"
            }
          }
        },
        "number_of_replicas" : "1",
        "uuid" : "f3rDAwgLRNuL4wMmFh6Xaw",
        "version" : {
          "created" : "7140099"
        }
      }
    }
  }
}

啟動應用程序,檢查是否可以使用 上面新建的索引:

啟動未報錯。

 

添加之前的數據(3條):添加成功。

測試之前試驗用的接口:

/news/getAll

/news/getById

/news/findByTitle

/news/findByTitleContaining 變化:輸入 神舟、十二號、空間 等詞的時候可以找到記錄了

/news/findByTitleOrContentText 變化:輸入單個字可能找不到了,但是,輸入一些 有意義的漢語詞語 或 內容中語句 是可以找到記錄的,

……

初步測試完畢,結果不是非常符合預期,比如,輸入“同時”,找不到記錄,但輸入相連的“與此同時”,卻能找到記錄

雖然用上了分詞器,但是,分詞器的優化,調整,配置,才是ES使用的重點之一吧。

 

小結:

本章介紹了分詞器的基本使用,中文、拼音分詞器的安裝及基本使用。來自博客園

分詞器,需要繼續學習才是。

 

》》》全文完《《《

 

第一篇ES的文章,寫了好幾天,不懂的還挺多,還需繼續研究才是。

去看看博文、ES官文、Spring Data ES官文,相信會有更全面、深入的了解。來自博客園

還有一些問題也待研究:數據存(同步)到ES、集群部署、動態擴容、原理……真多。

還有Kibana也要熟練使用才是,挺強大的。

 

參考文檔

1、Elasticsearch簡介與實戰

2、Elasticsearch拼音分詞和IK分詞的安裝及使用

3、elasticsearch系列三:索引詳解(分詞器、文檔管理、路由詳解(集群))

4、Elasticsearch(10) --- 內置分詞器、中文分詞器

5、ik分詞器搜不出單個中文詞

6、一文學會ES的API接口

7、ES 分詞器使用和配置

8、

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM