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版本哦,下載下來!
無PDF,網頁上可以搜索。
目錄
3.1、檢查elasticsearchRestHighLevelClient Bean並檢查
3.2、生成ElasticsearchRestTemplate Bean並檢查
3.3、使用Spring Data Elasticsearch Repositories存取數據
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 | 內部節點或集群與客戶端的交互方式 |
參考:百度百科-elasticsearch、Elasticsearch核心概念
elastic:n. 松緊帶; 橡皮筋; adj. 有彈性的; 靈活的;
本文介紹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 鏈接進入:
下載得到:
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
...
接下來,可以在 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中操作,這個工具真是好啊!還可以把這么多執行記錄保存下來,還有提示信息!來自博客園
應該還有更加強大、便捷的功能 待解鎖。
S.B.為ES提供了基本的自動配置功能。
S.B.提供了3種客戶端(clients):
- 官方的Java底層REST客戶端(Transport Client)
- 官方的Java高級REST客戶端(High Level REST Client)
- 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.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:
方式2:
"keyword": "" 返回所有數據
"keyword": "單個漢字" 可以找到記錄
"keyword": "一段文檔中較長的文字" 找不到記錄
"keyword": "標題" 找不到記錄
……
更多區別,請大家自行試驗。
我自己還要去好好看看文檔才是——Spring Data Elasticsearch - Reference Documentation。
本小結只是試驗了ES的基本用法,實際上還有更多高級的操作可以使用的,比如,聚合查詢,還需繼續探索。
在序章中提到了,要使用 分詞器 對用戶輸入的數據進行處理,然后存庫或搜索使用。
中文等象形文字 和 英文等字符組合型文字 的分詞方法是不同的。比如,英文中分詞使用空格(書寫的時候),但是中文呢?沒有空格,需要根據使用經驗來做分詞。
因此,在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也要熟練使用才是,挺強大的。
2、Elasticsearch拼音分詞和IK分詞的安裝及使用
3、elasticsearch系列三:索引詳解(分詞器、文檔管理、路由詳解(集群))
4、Elasticsearch(10) --- 內置分詞器、中文分詞器
8、